2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
10 * You may distribute under the terms of the GNU General Public License as
11 * specified in the README file that comes with the CVS source distribution.
15 * Create a Larry Wall format "patch" file between a previous release and the
16 * current head of a module, or between two releases. Can specify the
17 * release as either a date or a revision number.
26 static RETSIGTYPE patch_cleanup PROTO((void));
27 static Dtype patch_dirproc PROTO ((void *callerdat, const char *dir,
28 const char *repos, const char *update_dir,
30 static int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo));
31 static int patch_proc PROTO((int argc, char **argv, char *xwhere,
32 char *mwhere, char *mfile, int shorten,
33 int local_specified, char *mname, char *msg));
35 static int force_tag_match = 1;
36 static int patch_short = 0;
37 static int toptwo_diffs = 0;
38 static char *options = NULL;
39 static char *rev1 = NULL;
40 static int rev1_validated = 0;
41 static char *rev2 = NULL;
42 static int rev2_validated = 0;
43 static char *date1 = NULL;
44 static char *date2 = NULL;
45 static char *tmpfile1 = NULL;
46 static char *tmpfile2 = NULL;
47 static char *tmpfile3 = NULL;
48 static int unidiff = 0;
50 static const char *const patch_usage[] =
52 "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d] [-k kopt]\n",
53 " -r rev|-D date [-r rev2 | -D date2] modules...\n",
54 "\t-f\tForce a head revision match if tag/date not found.\n",
55 "\t-l\tLocal directory only, not recursive\n",
56 "\t-R\tProcess directories recursively.\n",
57 "\t-c\tContext diffs (default)\n",
58 "\t-u\tUnidiff format.\n",
59 "\t-s\tShort patch - one liner per file.\n",
60 "\t-t\tTop two diffs - last change made to the file.\n",
61 "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
62 "\t-k kopt\tSpecify keyword expansion mode.\n",
64 "\t-r rev\tRevision - symbolic or numeric.\n",
65 "(Specify the --help global option for a list of other help options)\n",
86 while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1)
92 /* The CVS 1.5 client sends these options (in addition to
93 Global_option requests), so we must ignore them. */
96 "-q or -Q must be specified before \"%s\"",
115 if (rev2 != NULL || date2 != NULL)
117 "no more than two revisions/dates can be specified");
118 if (rev1 != NULL || date1 != NULL)
119 date2 = Make_Date (optarg);
121 date1 = Make_Date (optarg);
124 if (rev2 != NULL || date2 != NULL)
126 "no more than two revisions/dates can be specified");
127 if (rev1 != NULL || date1 != NULL)
135 options = RCS_check_kflag (optarg);
138 /* This option is pretty seriously broken:
139 1. It is not clear what it does (does it change keyword
140 expansion behavior? If so, how? Or does it have
141 something to do with what version of RCS we are using?
142 Or the format we write RCS files in?).
143 2. Because both it and -k use the options variable,
144 specifying both -V and -k doesn't work.
145 3. At least as of CVS 1.9, it doesn't work (failed
146 assertion in RCS_checkout where it asserts that options
147 starts with -k). Few people seem to be complaining.
148 In the future (perhaps the near future), I have in mind
149 removing it entirely, and updating NEWS and cvs.texinfo,
150 but in case it is a good idea to give people more time
151 to complain if they would miss it, I'll just add this
152 quick and dirty error message for now. */
154 "the -V option is obsolete and should not be used");
156 if (atoi (optarg) <= 0)
157 error (1, 0, "must specify a version number to -V");
160 options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
161 (void) sprintf (options, "-V%s", optarg);
165 unidiff = 1; /* Unidiff */
167 case 'c': /* Context diff */
183 if (toptwo_diffs && patch_short)
184 error (1, 0, "-t and -s options are mutually exclusive");
185 if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
186 rev1 != NULL || rev2 != NULL))
187 error (1, 0, "must not specify revisions/dates with -t option!");
189 if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
190 rev1 == NULL && rev2 == NULL))
191 error (1, 0, "must specify at least one revision/date!");
192 if (date1 != NULL && date2 != NULL)
193 if (RCS_datecmp (date1, date2) >= 0)
194 error (1, 0, "second date must come after first date!");
196 /* if options is NULL, make it a NULL string */
198 options = xstrdup ("");
200 #ifdef CLIENT_SUPPORT
201 if (current_parsed_root->isremote)
203 /* We're the client side. Fire up the remote server. */
210 if (!force_tag_match)
220 option_with_arg ("-r", rev1);
222 client_senddate (date1);
224 option_with_arg ("-r", rev2);
226 client_senddate (date2);
227 if (options[0] != '\0')
232 for (i = 0; i < argc; ++i)
236 send_to_server ("rdiff\012", 0);
237 return get_responses_and_close ();
241 /* clean up if we get a signal */
243 (void)SIG_register (SIGABRT, patch_cleanup);
246 (void)SIG_register (SIGHUP, patch_cleanup);
249 (void)SIG_register (SIGINT, patch_cleanup);
252 (void)SIG_register (SIGQUIT, patch_cleanup);
255 (void)SIG_register (SIGPIPE, patch_cleanup);
258 (void)SIG_register (SIGTERM, patch_cleanup);
262 for (i = 0; i < argc; i++)
263 err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
264 (char *)NULL, 0, local, 0, 0, (char *)NULL);
274 * callback proc for doing the real work of patching
278 patch_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
296 repository = xmalloc (strlen (current_parsed_root->directory)
298 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
299 (void)sprintf (repository, "%s/%s",
300 current_parsed_root->directory, argv[0]);
301 where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
303 (void)strcpy (where, argv[0]);
305 /* if mfile isn't null, we need to set up to do only part of the module */
311 /* if the portion of the module is a path, put the dir part on repos */
312 if ((cp = strrchr (mfile, '/')) != NULL)
315 (void)strcat (repository, "/");
316 (void)strcat (repository, mfile);
317 (void)strcat (where, "/");
318 (void)strcat (where, mfile);
322 /* take care of the rest */
323 path = xmalloc (strlen (repository) + strlen (mfile) + 2);
324 (void)sprintf (path, "%s/%s", repository, mfile);
327 /* directory means repository gets the dir tacked on */
328 (void)strcpy (repository, path);
329 (void)strcat (where, "/");
330 (void)strcat (where, mfile);
342 /* cd to the starting repository */
343 if ( CVS_CHDIR (repository) < 0)
345 error (0, errno, "cannot chdir to %s", repository);
352 which = W_REPOS | W_ATTIC;
356 if (rev1 != NULL && !rev1_validated)
358 tag_check_valid (rev1, argc - 1, argv + 1, local_specified, 0,
362 if (rev2 != NULL && !rev2_validated)
364 tag_check_valid (rev2, argc - 1, argv + 1, local_specified, 0,
369 /* start the recursion processor */
370 err = start_recursion (patch_fileproc, (FILESDONEPROC)NULL, patch_dirproc,
371 (DIRLEAVEPROC)NULL, NULL,
372 argc - 1, argv + 1, local_specified,
373 which, 0, CVS_LOCK_READ, where, 1, repository);
383 * Called to examine a particular RCS file, as appropriate with the options
384 * that were set above.
388 patch_fileproc (callerdat, finfo)
390 struct file_info *finfo;
393 char *vers_tag, *vers_head;
395 char *rcs_orig = NULL;
397 FILE *fp1, *fp2, *fp3;
405 size_t line1_chars_allocated;
406 size_t line2_chars_allocated;
411 size_t darg_allocated = 0;
415 line1_chars_allocated = 0;
417 line2_chars_allocated = 0;
418 vers_tag = vers_head = NULL;
420 /* find the parsed rcs file */
421 if ((rcsfile = finfo->rcs) == NULL)
426 if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
429 rcs_orig = rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5);
430 (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);
432 /* if vers_head is NULL, may have been removed from the release */
433 if (isattic && rev2 == NULL && date2 == NULL)
437 vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
439 if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
448 if (vers_head == NULL)
455 date1 = xmalloc (MAXDATELEN);
457 if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (time_t)-1)
460 error (0, 0, "cannot find date in rcs file %s revision %s",
466 vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match,
468 if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
474 if ((vers_tag == NULL && vers_head == NULL) ||
475 (vers_tag != NULL && vers_head != NULL &&
476 strcmp (vers_head, vers_tag) == 0))
478 /* Nothing known about specified revs or
479 * not changed between releases.
485 if( patch_short && ( vers_tag == NULL || vers_head == NULL ) )
487 /* For adds & removes with a short patch requested, we can print our
488 * error message now and get out.
490 cvs_output ("File ", 0);
491 cvs_output (finfo->fullname, 0);
492 if (vers_tag == NULL)
494 cvs_output( " is new; ", 0 );
495 cvs_output( rev2 ? rev2 : date2 ? date2 : "current", 0 );
496 cvs_output( " revision ", 0 );
497 cvs_output (vers_head, 0);
498 cvs_output ("\n", 1);
502 cvs_output( " is removed; ", 0 );
503 cvs_output( rev1 ? rev1 : date1, 0 );
504 cvs_output( " revision ", 0 );
505 cvs_output( vers_tag, 0 );
506 cvs_output ("\n", 1);
512 /* Create 3 empty files. I'm not really sure there is any advantage
513 * to doing so now rather than just waiting until later.
515 * There is - cvs_temp_file opens the file so that it can guarantee that
516 * we have exclusive write access to the file. Unfortunately we spoil that
517 * by closing it and reopening it again. Of course any better solution
518 * requires that the RCS functions accept open file pointers rather than
521 if ((fp1 = cvs_temp_file (&tmpfile1)) == NULL)
523 error (0, errno, "cannot create temporary file %s",
524 tmpfile1 ? tmpfile1 : "(null)");
529 if (fclose (fp1) < 0)
530 error (0, errno, "warning: cannot close %s", tmpfile1);
531 if ((fp2 = cvs_temp_file (&tmpfile2)) == NULL)
533 error (0, errno, "cannot create temporary file %s",
534 tmpfile2 ? tmpfile2 : "(null)");
539 if (fclose (fp2) < 0)
540 error (0, errno, "warning: cannot close %s", tmpfile2);
541 if ((fp3 = cvs_temp_file (&tmpfile3)) == NULL)
543 error (0, errno, "cannot create temporary file %s",
544 tmpfile3 ? tmpfile3 : "(null)");
549 if (fclose (fp3) < 0)
550 error (0, errno, "warning: cannot close %s", tmpfile3);
552 if (vers_tag != NULL)
554 retcode = RCS_checkout (rcsfile, (char *)NULL, vers_tag,
555 rev1, options, tmpfile1,
556 (RCSCHECKOUTPROC)NULL, (void *)NULL);
560 "cannot check out revision %s of %s", vers_tag, rcs);
564 memset ((char *) &t, 0, sizeof (t));
565 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
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 (tmpfile1, &t);
571 else if (toptwo_diffs)
576 if (vers_head != NULL)
578 retcode = RCS_checkout (rcsfile, (char *)NULL, vers_head,
579 rev2, options, tmpfile2,
580 (RCSCHECKOUTPROC)NULL, (void *)NULL);
584 "cannot check out revision %s of %s", vers_head, rcs);
588 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
589 (char *)0, 0)) != -1)
590 /* I believe this timestamp only affects the dates in our diffs,
591 and therefore should be on the server, not the client. */
592 (void)utime (tmpfile2, &t);
595 if (unidiff) run_add_arg_p (&dargc, &darg_allocated, &dargv, "-u");
596 else run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c");
597 switch (diff_exec (tmpfile1, tmpfile2, NULL, NULL, dargc, dargv,
600 case -1: /* fork/wait failure */
601 error (1, errno, "fork for diff failed on %s", rcs);
603 case 0: /* nothing to do */
607 * The two revisions are really different, so read the first two
608 * lines of the diff output file, and munge them to include more
609 * reasonable file names that "patch" will understand, unless the
610 * user wanted a short patch. In that case, just output the short
615 cvs_output ("File ", 0);
616 cvs_output (finfo->fullname, 0);
617 cvs_output (" changed from revision ", 0);
618 cvs_output (vers_tag, 0);
619 cvs_output (" to ", 0);
620 cvs_output (vers_head, 0);
621 cvs_output ("\n", 1);
626 /* Output an "Index:" line for patch to use */
627 cvs_output ("Index: ", 0);
628 cvs_output (finfo->fullname, 0);
629 cvs_output ("\n", 1);
631 /* Now the munging. */
632 fp = open_file (tmpfile3, "r");
633 if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
634 getline (&line2, &line2_chars_allocated, fp) < 0)
638 failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
641 "failed to read diff file header %s for %s",
645 error (0, errno, "error closing %s", tmpfile3);
650 if (strncmp (line1, "*** ", 4) != 0 ||
651 strncmp (line2, "--- ", 4) != 0 ||
652 (cp1 = strchr (line1, '\t')) == NULL ||
653 (cp2 = strchr (line2, '\t')) == NULL)
655 error (0, 0, "invalid diff header for %s", rcs);
658 error (0, errno, "error closing %s", tmpfile3);
664 if (strncmp (line1, "--- ", 4) != 0 ||
665 strncmp (line2, "+++ ", 4) != 0 ||
666 (cp1 = strchr (line1, '\t')) == NULL ||
667 (cp2 = strchr (line2, '\t')) == NULL)
669 error (0, 0, "invalid unidiff header for %s", rcs);
672 error (0, errno, "error closing %s", tmpfile3);
676 assert (current_parsed_root != NULL);
677 assert (current_parsed_root->directory != NULL);
679 strippath = xmalloc (strlen (current_parsed_root->directory)
681 (void)sprintf (strippath, "%s/",
682 current_parsed_root->directory);
685 strippath = xstrdup (REPOS_STRIP); */
686 if (strncmp (rcs, strippath, strlen (strippath)) == 0)
687 rcs += strlen (strippath);
689 if (vers_tag != NULL)
691 file1 = xmalloc (strlen (finfo->fullname)
694 (void)sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
698 file1 = xstrdup (DEVNULL);
700 file2 = xmalloc (strlen (finfo->fullname)
701 + (vers_head != NULL ? strlen (vers_head) : 10)
703 (void)sprintf (file2, "%s:%s", finfo->fullname,
704 vers_head ? vers_head : "removed");
706 /* Note that the string "diff" is specified by POSIX (for -c)
707 and is part of the diff output format, not the name of a
711 cvs_output ("diff -u ", 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);
724 cvs_output ("diff -c ", 0);
725 cvs_output (file1, 0);
727 cvs_output (file2, 0);
728 cvs_output ("\n", 1);
730 cvs_output ("*** ", 0);
731 cvs_output (file1, 0);
733 cvs_output ("--- ", 0);
736 cvs_output (finfo->fullname, 0);
739 /* spew the rest of the diff out */
741 = getline (&line1, &line1_chars_allocated, fp))
743 cvs_output (line1, 0);
744 if (line_length < 0 && !feof (fp))
745 error (0, errno, "cannot read %s", tmpfile3);
748 error (0, errno, "cannot close %s", tmpfile3);
753 error (0, 0, "diff failed for %s", finfo->fullname);
760 if (tmpfile1 != NULL)
762 if (CVS_UNLINK (tmpfile1) < 0)
763 error (0, errno, "cannot unlink %s", tmpfile1);
767 if (tmpfile2 != NULL)
769 if (CVS_UNLINK (tmpfile2) < 0)
770 error (0, errno, "cannot unlink %s", tmpfile2);
774 if (tmpfile3 != NULL)
776 if (CVS_UNLINK (tmpfile3) < 0)
777 error (0, errno, "cannot unlink %s", tmpfile3);
784 run_arg_free_p (dargc, dargv);
789 if (vers_tag != NULL)
791 if (vers_head != NULL)
801 * Print a warm fuzzy message
805 patch_dirproc (callerdat, dir, repos, update_dir, entries)
809 const char *update_dir;
813 error (0, 0, "Diffing %s", update_dir);
818 * Clean up temporary files
823 /* Note that the checks for existence_error are because we are
824 called from a signal handler, without SIG_begincrsect, so
825 we don't know whether the files got created. */
827 if (tmpfile1 != NULL)
829 if (unlink_file (tmpfile1) < 0
830 && !existence_error (errno))
831 error (0, errno, "cannot remove %s", tmpfile1);
834 if (tmpfile2 != NULL)
836 if (unlink_file (tmpfile2) < 0
837 && !existence_error (errno))
838 error (0, errno, "cannot remove %s", tmpfile2);
841 if (tmpfile3 != NULL)
843 if (unlink_file (tmpfile3) < 0
844 && !existence_error (errno))
845 error (0, errno, "cannot remove %s", tmpfile3);
848 tmpfile1 = tmpfile2 = tmpfile3 = NULL;