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.
18 static RETSIGTYPE patch_cleanup (void);
19 static Dtype patch_dirproc (void *callerdat, const char *dir,
20 const char *repos, const char *update_dir,
22 static int patch_fileproc (void *callerdat, struct file_info *finfo);
23 static int patch_proc (int argc, char **argv, char *xwhere,
24 char *mwhere, char *mfile, int shorten,
25 int local_specified, char *mname, char *msg);
27 static int force_tag_match = 1;
28 static int patch_short = 0;
29 static int toptwo_diffs = 0;
30 static char *options = NULL;
31 static char *rev1 = NULL;
32 static int rev1_validated = 0;
33 static char *rev2 = NULL;
34 static int rev2_validated = 0;
35 static char *date1 = NULL;
36 static char *date2 = NULL;
37 static char *tmpfile1 = NULL;
38 static char *tmpfile2 = NULL;
39 static char *tmpfile3 = NULL;
40 static int unidiff = 0;
42 static const char *const patch_usage[] =
44 "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d]\n",
45 " -r rev|-D date [-r rev2 | -D date2] modules...\n",
46 "\t-f\tForce a head revision match if tag/date not found.\n",
47 "\t-l\tLocal directory only, not recursive\n",
48 "\t-R\tProcess directories recursively.\n",
49 "\t-c\tContext diffs (default)\n",
50 "\t-u\tUnidiff format.\n",
51 "\t-s\tShort patch - one liner per file.\n",
52 "\t-t\tTop two diffs - last change made to the file.\n",
54 "\t-r rev\tRevision - symbolic or numeric.\n",
55 "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
56 "(Specify the --help global option for a list of other help options)\n",
63 patch (int argc, char **argv)
75 while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1)
82 /* The CVS 1.5 client sends these options (in addition to
83 Global_option requests), so we must ignore them. */
87 "-q or -Q must be specified before \"%s\"",
106 if (rev2 != NULL || date2 != NULL)
108 "no more than two revisions/dates can be specified");
109 if (rev1 != NULL || date1 != NULL)
110 date2 = Make_Date (optarg);
112 date1 = Make_Date (optarg);
115 if (rev2 != NULL || date2 != NULL)
117 "no more than two revisions/dates can be specified");
118 if (rev1 != NULL || date1 != NULL)
126 options = RCS_check_kflag (optarg);
129 /* This option is pretty seriously broken:
130 1. It is not clear what it does (does it change keyword
131 expansion behavior? If so, how? Or does it have
132 something to do with what version of RCS we are using?
133 Or the format we write RCS files in?).
134 2. Because both it and -k use the options variable,
135 specifying both -V and -k doesn't work.
136 3. At least as of CVS 1.9, it doesn't work (failed
137 assertion in RCS_checkout where it asserts that options
138 starts with -k). Few people seem to be complaining.
139 In the future (perhaps the near future), I have in mind
140 removing it entirely, and updating NEWS and cvs.texinfo,
141 but in case it is a good idea to give people more time
142 to complain if they would miss it, I'll just add this
143 quick and dirty error message for now. */
145 "the -V option is obsolete and should not be used");
147 if (atoi (optarg) <= 0)
148 error (1, 0, "must specify a version number to -V");
151 options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
152 (void) sprintf (options, "-V%s", optarg);
156 unidiff = 1; /* Unidiff */
158 case 'c': /* Context diff */
174 if (toptwo_diffs && patch_short)
175 error (1, 0, "-t and -s options are mutually exclusive");
176 if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
177 rev1 != NULL || rev2 != NULL))
178 error (1, 0, "must not specify revisions/dates with -t option!");
180 if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
181 rev1 == NULL && rev2 == NULL))
182 error (1, 0, "must specify at least one revision/date!");
183 if (date1 != NULL && date2 != NULL)
184 if (RCS_datecmp (date1, date2) >= 0)
185 error (1, 0, "second date must come after first date!");
187 /* if options is NULL, make it a NULL string */
189 options = xstrdup ("");
191 #ifdef CLIENT_SUPPORT
192 if (current_parsed_root->isremote)
194 /* We're the client side. Fire up the remote server. */
201 if (!force_tag_match)
211 option_with_arg ("-r", rev1);
213 client_senddate (date1);
215 option_with_arg ("-r", rev2);
217 client_senddate (date2);
218 if (options[0] != '\0')
223 for (i = 0; i < argc; ++i)
227 send_to_server ("rdiff\012", 0);
228 return get_responses_and_close ();
232 /* clean up if we get a signal */
234 (void)SIG_register (SIGABRT, patch_cleanup);
237 (void)SIG_register (SIGHUP, patch_cleanup);
240 (void)SIG_register (SIGINT, patch_cleanup);
243 (void)SIG_register (SIGQUIT, patch_cleanup);
246 (void)SIG_register (SIGPIPE, patch_cleanup);
249 (void)SIG_register (SIGTERM, patch_cleanup);
253 for (i = 0; i < argc; i++)
254 err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
255 (char *)NULL, 0, local, 0, 0, (char *)NULL);
265 * callback proc for doing the real work of patching
269 patch_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
270 int shorten, int local_specified, char *mname, char *msg)
278 TRACE ( TRACE_FUNCTION, "patch_proc ( %s, %s, %s, %d, %d, %s, %s )",
279 xwhere ? xwhere : "(null)",
280 mwhere ? mwhere : "(null)",
281 mfile ? mfile : "(null)",
282 shorten, local_specified,
283 mname ? mname : "(null)",
284 msg ? msg : "(null)" );
286 repository = xmalloc (strlen (current_parsed_root->directory)
288 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
289 (void)sprintf (repository, "%s/%s",
290 current_parsed_root->directory, argv[0]);
291 where = xmalloc (strlen (argv[0])
292 + (mfile == NULL ? 0 : strlen (mfile) + 1)
294 (void)strcpy (where, argv[0]);
296 /* if mfile isn't null, we need to set up to do only part of the module */
302 /* if the portion of the module is a path, put the dir part on repos */
303 if ((cp = strrchr (mfile, '/')) != NULL)
306 (void)strcat (repository, "/");
307 (void)strcat (repository, mfile);
308 (void)strcat (where, "/");
309 (void)strcat (where, mfile);
313 /* take care of the rest */
314 path = xmalloc (strlen (repository) + strlen (mfile) + 2);
315 (void)sprintf (path, "%s/%s", repository, mfile);
318 /* directory means repository gets the dir tacked on */
319 (void)strcpy (repository, path);
320 (void)strcat (where, "/");
321 (void)strcat (where, mfile);
333 /* cd to the starting repository */
334 if (CVS_CHDIR (repository) < 0)
336 error (0, errno, "cannot chdir to %s", repository);
342 which = W_REPOS | W_ATTIC;
346 if (rev1 != NULL && !rev1_validated)
348 tag_check_valid (rev1, argc - 1, argv + 1, local_specified, 0,
352 if (rev2 != NULL && !rev2_validated)
354 tag_check_valid (rev2, argc - 1, argv + 1, local_specified, 0,
359 /* start the recursion processor */
360 err = start_recursion (patch_fileproc, NULL, patch_dirproc, NULL, NULL,
361 argc - 1, argv + 1, local_specified,
362 which, 0, CVS_LOCK_READ, where, 1, repository );
372 * Called to examine a particular RCS file, as appropriate with the options
373 * that were set above.
377 patch_fileproc (void *callerdat, struct file_info *finfo)
380 char *vers_tag, *vers_head;
383 FILE *fp1, *fp2, *fp3;
391 size_t line1_chars_allocated;
392 size_t line2_chars_allocated;
398 line1_chars_allocated = 0;
400 line2_chars_allocated = 0;
401 vers_tag = vers_head = NULL;
403 /* find the parsed rcs file */
404 if ((rcsfile = finfo->rcs) == NULL)
409 if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
412 rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5);
413 (void)sprintf (rcs, "%s%s", finfo->file, RCSEXT);
415 /* if vers_head is NULL, may have been removed from the release */
416 if (isattic && rev2 == NULL && date2 == NULL)
420 vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
422 if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
431 if (vers_head == NULL)
438 date1 = xmalloc (MAXDATELEN);
440 if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (time_t)-1)
443 error (0, 0, "cannot find date in rcs file %s revision %s",
449 vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, NULL);
450 if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
456 if ((vers_tag == NULL && vers_head == NULL) ||
457 (vers_tag != NULL && vers_head != NULL &&
458 strcmp (vers_head, vers_tag) == 0))
460 /* Nothing known about specified revs or
461 * not changed between releases.
467 if (patch_short && (vers_tag == NULL || vers_head == NULL))
469 /* For adds & removes with a short patch requested, we can print our
470 * error message now and get out.
472 cvs_output ("File ", 0);
473 cvs_output (finfo->fullname, 0);
474 if (vers_tag == NULL)
476 cvs_output (" is new; ", 0);
477 cvs_output (rev2 ? rev2 : date2 ? date2 : "current", 0);
478 cvs_output (" revision ", 0);
479 cvs_output (vers_head, 0);
480 cvs_output ("\n", 1);
484 cvs_output (" is removed; ", 0);
485 cvs_output (rev1 ? rev1 : date1, 0);
486 cvs_output (" revision ", 0);
487 cvs_output (vers_tag, 0);
488 cvs_output ("\n", 1);
494 /* Create 3 empty files. I'm not really sure there is any advantage
495 * to doing so now rather than just waiting until later.
497 * There is - cvs_temp_file opens the file so that it can guarantee that
498 * we have exclusive write access to the file. Unfortunately we spoil that
499 * by closing it and reopening it again. Of course any better solution
500 * requires that the RCS functions accept open file pointers rather than
503 if ((fp1 = cvs_temp_file (&tmpfile1)) == NULL)
505 error (0, errno, "cannot create temporary file %s", tmpfile1);
510 if (fclose (fp1) < 0)
511 error (0, errno, "warning: cannot close %s", tmpfile1);
512 if ((fp2 = cvs_temp_file (&tmpfile2)) == NULL)
514 error (0, errno, "cannot create temporary file %s", tmpfile2);
519 if (fclose (fp2) < 0)
520 error (0, errno, "warning: cannot close %s", tmpfile2);
521 if ((fp3 = cvs_temp_file (&tmpfile3)) == NULL)
523 error (0, errno, "cannot create temporary file %s", tmpfile3);
528 if (fclose (fp3) < 0)
529 error (0, errno, "warning: cannot close %s", tmpfile3);
531 if (vers_tag != NULL)
533 retcode = RCS_checkout (rcsfile, NULL, vers_tag, rev1, options,
534 tmpfile1, NULL, NULL);
538 "cannot check out revision %s of %s", vers_tag, rcs);
542 memset ((char *) &t, 0, sizeof (t));
543 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
544 (char *) 0, 0)) != -1)
545 /* I believe this timestamp only affects the dates in our diffs,
546 and therefore should be on the server, not the client. */
547 (void)utime (tmpfile1, &t);
549 else if (toptwo_diffs)
554 if (vers_head != NULL)
556 retcode = RCS_checkout (rcsfile, NULL, vers_head, rev2, options,
557 tmpfile2, NULL, NULL);
561 "cannot check out revision %s of %s", vers_head, rcs);
565 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
566 (char *)0, 0)) != -1)
567 /* I believe this timestamp only affects the dates in our diffs,
568 and therefore should be on the server, not the client. */
569 (void)utime (tmpfile2, &t);
572 switch (diff_exec (tmpfile1, tmpfile2, NULL, NULL, unidiff ? "-u" : "-c",
575 case -1: /* fork/wait failure */
576 error (1, errno, "fork for diff failed on %s", rcs);
578 case 0: /* nothing to do */
582 * The two revisions are really different, so read the first two
583 * lines of the diff output file, and munge them to include more
584 * reasonable file names that "patch" will understand, unless the
585 * user wanted a short patch. In that case, just output the short
590 cvs_output ("File ", 0);
591 cvs_output (finfo->fullname, 0);
592 cvs_output (" changed from revision ", 0);
593 cvs_output (vers_tag, 0);
594 cvs_output (" to ", 0);
595 cvs_output (vers_head, 0);
596 cvs_output ("\n", 1);
601 /* Output an "Index:" line for patch to use */
602 cvs_output ("Index: ", 0);
603 cvs_output (finfo->fullname, 0);
604 cvs_output ("\n", 1);
606 /* Now the munging. */
607 fp = open_file (tmpfile3, "r");
608 if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
609 getline (&line2, &line2_chars_allocated, fp) < 0)
613 failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
616 "failed to read diff file header %s for %s",
620 error (0, errno, "error closing %s", tmpfile3);
625 if (strncmp (line1, "*** ", 4) != 0 ||
626 strncmp (line2, "--- ", 4) != 0 ||
627 (cp1 = strchr (line1, '\t')) == NULL ||
628 (cp2 = strchr (line2, '\t')) == NULL)
630 error (0, 0, "invalid diff header for %s", rcs);
633 error (0, errno, "error closing %s", tmpfile3);
639 if (strncmp (line1, "--- ", 4) != 0 ||
640 strncmp (line2, "+++ ", 4) != 0 ||
641 (cp1 = strchr (line1, '\t')) == NULL ||
642 (cp2 = strchr (line2, '\t')) == NULL)
644 error (0, 0, "invalid unidiff header for %s", rcs);
647 error (0, errno, "error closing %s", tmpfile3);
651 assert (current_parsed_root != NULL);
652 assert (current_parsed_root->directory != NULL);
654 strippath = xmalloc (strlen (current_parsed_root->directory)
656 (void)sprintf (strippath, "%s/",
657 current_parsed_root->directory);
660 strippath = xstrdup (REPOS_STRIP); */
661 if (strncmp (rcs, strippath, strlen (strippath)) == 0)
662 rcs += strlen (strippath);
664 if (vers_tag != NULL)
666 file1 = xmalloc (strlen (finfo->fullname)
669 (void)sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
673 file1 = xstrdup (DEVNULL);
675 file2 = xmalloc (strlen (finfo->fullname)
676 + (vers_head != NULL ? strlen (vers_head) : 10)
678 (void)sprintf (file2, "%s:%s", finfo->fullname,
679 vers_head ? vers_head : "removed");
681 /* Note that the string "diff" is specified by POSIX (for -c)
682 and is part of the diff output format, not the name of a
686 cvs_output ("diff -u ", 0);
687 cvs_output (file1, 0);
689 cvs_output (file2, 0);
690 cvs_output ("\n", 1);
692 cvs_output ("--- ", 0);
693 cvs_output (file1, 0);
695 cvs_output ("+++ ", 0);
699 cvs_output ("diff -c ", 0);
700 cvs_output (file1, 0);
702 cvs_output (file2, 0);
703 cvs_output ("\n", 1);
705 cvs_output ("*** ", 0);
706 cvs_output (file1, 0);
708 cvs_output ("--- ", 0);
711 cvs_output (finfo->fullname, 0);
714 /* spew the rest of the diff out */
716 = getline (&line1, &line1_chars_allocated, fp))
718 cvs_output (line1, 0);
719 if (line_length < 0 && !feof (fp))
720 error (0, errno, "cannot read %s", tmpfile3);
723 error (0, errno, "cannot close %s", tmpfile3);
728 error (0, 0, "diff failed for %s", finfo->fullname);
735 if (CVS_UNLINK (tmpfile1) < 0)
736 error (0, errno, "cannot unlink %s", tmpfile1);
737 if (CVS_UNLINK (tmpfile2) < 0)
738 error (0, errno, "cannot unlink %s", tmpfile2);
739 if (CVS_UNLINK (tmpfile3) < 0)
740 error (0, errno, "cannot unlink %s", tmpfile3);
744 tmpfile1 = tmpfile2 = tmpfile3 = NULL;
747 if (vers_tag != NULL)
749 if (vers_head != NULL)
759 * Print a warm fuzzy message
763 patch_dirproc (void *callerdat, const char *dir, const char *repos,
764 const char *update_dir, List *entries)
767 error (0, 0, "Diffing %s", update_dir);
774 * Clean up temporary files
779 /* Note that the checks for existence_error are because we are
780 called from a signal handler, without SIG_begincrsect, so
781 we don't know whether the files got created. */
783 if (tmpfile1 != NULL)
785 if (unlink_file (tmpfile1) < 0
786 && !existence_error (errno))
787 error (0, errno, "cannot remove %s", tmpfile1);
790 if (tmpfile2 != NULL)
792 if (unlink_file (tmpfile2) < 0
793 && !existence_error (errno))
794 error (0, errno, "cannot remove %s", tmpfile2);
797 if (tmpfile3 != NULL)
799 if (unlink_file (tmpfile3) < 0
800 && !existence_error (errno))
801 error (0, errno, "cannot remove %s", tmpfile3);
804 tmpfile1 = tmpfile2 = tmpfile3 = NULL;