1 /* 2 * Copyright (c) 1992, Brian Berliner and Jeff Polk 3 * Copyright (c) 1989-1992, Brian Berliner 4 * 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 1.4 kit. 7 * 8 * Patch 9 * 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. 13 */ 14 15 #include "cvs.h" 16 #include "getline.h" 17 18 static RETSIGTYPE patch_cleanup PROTO((void)); 19 static Dtype patch_dirproc PROTO ((void *callerdat, char *dir, 20 char *repos, char *update_dir, 21 List *entries)); 22 static int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 23 static int patch_proc PROTO((int *pargc, char **argv, char *xwhere, 24 char *mwhere, char *mfile, int shorten, 25 int local_specified, char *mname, char *msg)); 26 27 static int force_tag_match = 1; 28 static int patch_short = 0; 29 static int toptwo_diffs = 0; 30 static int local = 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; 42 43 static const char *const patch_usage[] = 44 { 45 "Usage: %s %s [-fl] [-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-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", 53 "\t-D date\tDate.\n", 54 "\t-r rev\tRevision - symbolic or numeric.\n", 55 "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n", 56 NULL 57 }; 58 59 int 60 patch (argc, argv) 61 int argc; 62 char **argv; 63 { 64 register int i; 65 int c; 66 int err = 0; 67 DBM *db; 68 69 if (argc == -1) 70 usage (patch_usage); 71 72 optind = 1; 73 while ((c = getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1) 74 { 75 switch (c) 76 { 77 case 'Q': 78 case 'q': 79 #ifdef SERVER_SUPPORT 80 /* The CVS 1.5 client sends these options (in addition to 81 Global_option requests), so we must ignore them. */ 82 if (!server_active) 83 #endif 84 error (1, 0, 85 "-q or -Q must be specified before \"%s\"", 86 command_name); 87 break; 88 case 'f': 89 force_tag_match = 0; 90 break; 91 case 'l': 92 local = 1; 93 break; 94 case 'R': 95 local = 0; 96 break; 97 case 't': 98 toptwo_diffs = 1; 99 break; 100 case 's': 101 patch_short = 1; 102 break; 103 case 'D': 104 if (rev2 != NULL || date2 != NULL) 105 error (1, 0, 106 "no more than two revisions/dates can be specified"); 107 if (rev1 != NULL || date1 != NULL) 108 date2 = Make_Date (optarg); 109 else 110 date1 = Make_Date (optarg); 111 break; 112 case 'r': 113 if (rev2 != NULL || date2 != NULL) 114 error (1, 0, 115 "no more than two revisions/dates can be specified"); 116 if (rev1 != NULL || date1 != NULL) 117 rev2 = optarg; 118 else 119 rev1 = optarg; 120 break; 121 case 'k': 122 if (options) 123 free (options); 124 options = RCS_check_kflag (optarg); 125 break; 126 case 'V': 127 if (atoi (optarg) <= 0) 128 error (1, 0, "must specify a version number to -V"); 129 if (options) 130 free (options); 131 options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */ 132 (void) sprintf (options, "-V%s", optarg); 133 break; 134 case 'u': 135 unidiff = 1; /* Unidiff */ 136 break; 137 case 'c': /* Context diff */ 138 unidiff = 0; 139 break; 140 case '?': 141 default: 142 usage (patch_usage); 143 break; 144 } 145 } 146 argc -= optind; 147 argv += optind; 148 149 /* Sanity checks */ 150 if (argc < 1) 151 usage (patch_usage); 152 153 if (toptwo_diffs && patch_short) 154 error (1, 0, "-t and -s options are mutually exclusive"); 155 if (toptwo_diffs && (date1 != NULL || date2 != NULL || 156 rev1 != NULL || rev2 != NULL)) 157 error (1, 0, "must not specify revisions/dates with -t option!"); 158 159 if (!toptwo_diffs && (date1 == NULL && date2 == NULL && 160 rev1 == NULL && rev2 == NULL)) 161 error (1, 0, "must specify at least one revision/date!"); 162 if (date1 != NULL && date2 != NULL) 163 if (RCS_datecmp (date1, date2) >= 0) 164 error (1, 0, "second date must come after first date!"); 165 166 /* if options is NULL, make it a NULL string */ 167 if (options == NULL) 168 options = xstrdup (""); 169 170 #ifdef CLIENT_SUPPORT 171 if (client_active) 172 { 173 /* We're the client side. Fire up the remote server. */ 174 start_server (); 175 176 ign_setup (); 177 178 if (local) 179 send_arg("-l"); 180 if (!force_tag_match) 181 send_arg("-f"); 182 if (toptwo_diffs) 183 send_arg("-t"); 184 if (patch_short) 185 send_arg("-s"); 186 if (unidiff) 187 send_arg("-u"); 188 189 if (rev1) 190 option_with_arg ("-r", rev1); 191 if (date1) 192 client_senddate (date1); 193 if (rev2) 194 option_with_arg ("-r", rev2); 195 if (date2) 196 client_senddate (date2); 197 if (options[0] != '\0') 198 send_arg (options); 199 200 { 201 int i; 202 for (i = 0; i < argc; ++i) 203 send_arg (argv[i]); 204 } 205 206 send_to_server ("rdiff\012", 0); 207 return get_responses_and_close (); 208 } 209 #endif 210 211 /* clean up if we get a signal */ 212 #ifdef SIGHUP 213 (void) SIG_register (SIGHUP, patch_cleanup); 214 #endif 215 #ifdef SIGINT 216 (void) SIG_register (SIGINT, patch_cleanup); 217 #endif 218 #ifdef SIGQUIT 219 (void) SIG_register (SIGQUIT, patch_cleanup); 220 #endif 221 #ifdef SIGPIPE 222 (void) SIG_register (SIGPIPE, patch_cleanup); 223 #endif 224 #ifdef SIGTERM 225 (void) SIG_register (SIGTERM, patch_cleanup); 226 #endif 227 228 db = open_module (); 229 for (i = 0; i < argc; i++) 230 err += do_module (db, argv[i], PATCH, "Patching", patch_proc, 231 (char *) NULL, 0, 0, 0, (char *) NULL); 232 close_module (db); 233 free (options); 234 patch_cleanup (); 235 return (err); 236 } 237 238 /* 239 * callback proc for doing the real work of patching 240 */ 241 /* ARGSUSED */ 242 static char where[PATH_MAX]; 243 static int 244 patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified, 245 mname, msg) 246 int *pargc; 247 char **argv; 248 char *xwhere; 249 char *mwhere; 250 char *mfile; 251 int shorten; 252 int local_specified; 253 char *mname; 254 char *msg; 255 { 256 int err = 0; 257 int which; 258 char repository[PATH_MAX]; 259 260 (void) sprintf (repository, "%s/%s", CVSroot_directory, argv[0]); 261 (void) strcpy (where, argv[0]); 262 263 /* if mfile isn't null, we need to set up to do only part of the module */ 264 if (mfile != NULL) 265 { 266 char *cp; 267 char path[PATH_MAX]; 268 269 /* if the portion of the module is a path, put the dir part on repos */ 270 if ((cp = strrchr (mfile, '/')) != NULL) 271 { 272 *cp = '\0'; 273 (void) strcat (repository, "/"); 274 (void) strcat (repository, mfile); 275 (void) strcat (where, "/"); 276 (void) strcat (where, mfile); 277 mfile = cp + 1; 278 } 279 280 /* take care of the rest */ 281 (void) sprintf (path, "%s/%s", repository, mfile); 282 if (isdir (path)) 283 { 284 /* directory means repository gets the dir tacked on */ 285 (void) strcpy (repository, path); 286 (void) strcat (where, "/"); 287 (void) strcat (where, mfile); 288 } 289 else 290 { 291 int i; 292 293 /* a file means muck argv */ 294 for (i = 1; i < *pargc; i++) 295 free (argv[i]); 296 argv[1] = xstrdup (mfile); 297 (*pargc) = 2; 298 } 299 } 300 301 /* cd to the starting repository */ 302 if ( CVS_CHDIR (repository) < 0) 303 { 304 error (0, errno, "cannot chdir to %s", repository); 305 return (1); 306 } 307 308 if (force_tag_match) 309 which = W_REPOS | W_ATTIC; 310 else 311 which = W_REPOS; 312 313 if (rev1 != NULL && !rev1_validated) 314 { 315 tag_check_valid (rev1, *pargc - 1, argv + 1, local, 0, NULL); 316 rev1_validated = 1; 317 } 318 if (rev2 != NULL && !rev2_validated) 319 { 320 tag_check_valid (rev2, *pargc - 1, argv + 1, local, 0, NULL); 321 rev2_validated = 1; 322 } 323 324 /* start the recursion processor */ 325 err = start_recursion (patch_fileproc, (FILESDONEPROC) NULL, patch_dirproc, 326 (DIRLEAVEPROC) NULL, NULL, 327 *pargc - 1, argv + 1, local, 328 which, 0, 1, where, 1); 329 330 return (err); 331 } 332 333 /* 334 * Called to examine a particular RCS file, as appropriate with the options 335 * that were set above. 336 */ 337 /* ARGSUSED */ 338 static int 339 patch_fileproc (callerdat, finfo) 340 void *callerdat; 341 struct file_info *finfo; 342 { 343 struct utimbuf t; 344 char *vers_tag, *vers_head; 345 char rcsspace[1][PATH_MAX]; 346 char *rcs = rcsspace[0]; 347 RCSNode *rcsfile; 348 FILE *fp1, *fp2, *fp3; 349 int ret = 0; 350 int isattic = 0; 351 int retcode = 0; 352 char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX]; 353 char *line1, *line2; 354 size_t line1_chars_allocated; 355 size_t line2_chars_allocated; 356 char *cp1, *cp2; 357 FILE *fp; 358 359 line1 = NULL; 360 line1_chars_allocated = 0; 361 line2 = NULL; 362 line2_chars_allocated = 0; 363 364 /* find the parsed rcs file */ 365 if ((rcsfile = finfo->rcs) == NULL) 366 return (1); 367 if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) 368 isattic = 1; 369 370 (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT); 371 372 /* if vers_head is NULL, may have been removed from the release */ 373 if (isattic && rev2 == NULL && date2 == NULL) 374 vers_head = NULL; 375 else 376 { 377 vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match, 378 (int *) NULL); 379 if (vers_head != NULL && RCS_isdead (rcsfile, vers_head)) 380 { 381 free (vers_head); 382 vers_head = NULL; 383 } 384 } 385 386 if (toptwo_diffs) 387 { 388 if (vers_head == NULL) 389 return (1); 390 391 if (!date1) 392 date1 = xmalloc (50); /* plenty big :-) */ 393 *date1 = '\0'; 394 if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1) 395 { 396 if (!really_quiet) 397 error (0, 0, "cannot find date in rcs file %s revision %s", 398 rcs, vers_head); 399 return (1); 400 } 401 } 402 vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, 403 (int *) NULL); 404 if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag)) 405 { 406 free (vers_tag); 407 vers_tag = NULL; 408 } 409 410 if (vers_tag == NULL && vers_head == NULL) 411 return (0); /* nothing known about specified revs */ 412 413 if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0) 414 return (0); /* not changed between releases */ 415 416 if (patch_short) 417 { 418 (void) printf ("File %s ", finfo->fullname); 419 if (vers_tag == NULL) 420 (void) printf ("is new; current revision %s\n", vers_head); 421 else if (vers_head == NULL) 422 { 423 (void) printf ("is removed; not included in "); 424 if (rev2 != NULL) 425 (void) printf ("release tag %s", rev2); 426 else if (date2 != NULL) 427 (void) printf ("release date %s", date2); 428 else 429 (void) printf ("current release"); 430 (void) printf ("\n"); 431 } 432 else 433 (void) printf ("changed from revision %s to %s\n", 434 vers_tag, vers_head); 435 return (0); 436 } 437 tmpfile1 = cvs_temp_name (); 438 if ((fp1 = CVS_FOPEN (tmpfile1, "w+")) != NULL) 439 (void) fclose (fp1); 440 tmpfile2 = cvs_temp_name (); 441 if ((fp2 = CVS_FOPEN (tmpfile2, "w+")) != NULL) 442 (void) fclose (fp2); 443 tmpfile3 = cvs_temp_name (); 444 if ((fp3 = CVS_FOPEN (tmpfile3, "w+")) != NULL) 445 (void) fclose (fp3); 446 if (fp1 == NULL || fp2 == NULL || fp3 == NULL) 447 { 448 /* FIXME: should be printing a proper error message, with errno-based 449 message, and the filename which we could not create. */ 450 error (0, 0, "cannot create temporary files"); 451 ret = 1; 452 goto out; 453 } 454 if (vers_tag != NULL) 455 { 456 retcode = RCS_checkout (rcsfile, (char *) NULL, vers_tag, 457 rev1, options, tmpfile1); 458 if (retcode != 0) 459 { 460 if (!really_quiet) 461 error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, 462 "co of revision %s in %s failed", vers_tag, rcs); 463 ret = 1; 464 goto out; 465 } 466 memset ((char *) &t, 0, sizeof (t)); 467 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag, 468 (char *) 0, 0)) != -1) 469 (void) utime (tmpfile1, &t); 470 } 471 else if (toptwo_diffs) 472 { 473 ret = 1; 474 goto out; 475 } 476 if (vers_head != NULL) 477 { 478 retcode = RCS_checkout (rcsfile, (char *) NULL, vers_head, 479 rev2, options, tmpfile2); 480 if (retcode != 0) 481 { 482 if (!really_quiet) 483 error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, 484 "co of revision %s in %s failed", vers_head, rcs); 485 ret = 1; 486 goto out; 487 } 488 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head, 489 (char *) 0, 0)) != -1) 490 (void) utime (tmpfile2, &t); 491 } 492 run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c'); 493 run_arg (tmpfile1); 494 run_arg (tmpfile2); 495 496 switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_REALLY)) 497 { 498 case -1: /* fork/wait failure */ 499 error (1, errno, "fork for diff failed on %s", rcs); 500 break; 501 case 0: /* nothing to do */ 502 break; 503 case 1: 504 /* 505 * The two revisions are really different, so read the first two 506 * lines of the diff output file, and munge them to include more 507 * reasonable file names that "patch" will understand. 508 */ 509 510 /* Output an "Index:" line for patch to use */ 511 (void) fflush (stdout); 512 (void) printf ("Index: %s\n", finfo->fullname); 513 (void) fflush (stdout); 514 515 fp = open_file (tmpfile3, "r"); 516 if (getline (&line1, &line1_chars_allocated, fp) < 0 || 517 getline (&line2, &line2_chars_allocated, fp) < 0) 518 { 519 error (0, errno, "failed to read diff file header %s for %s", 520 tmpfile3, rcs); 521 ret = 1; 522 (void) fclose (fp); 523 goto out; 524 } 525 if (!unidiff) 526 { 527 if (strncmp (line1, "*** ", 4) != 0 || 528 strncmp (line2, "--- ", 4) != 0 || 529 (cp1 = strchr (line1, '\t')) == NULL || 530 (cp2 = strchr (line2, '\t')) == NULL) 531 { 532 error (0, 0, "invalid diff header for %s", rcs); 533 ret = 1; 534 (void) fclose (fp); 535 goto out; 536 } 537 } 538 else 539 { 540 if (strncmp (line1, "--- ", 4) != 0 || 541 strncmp (line2, "+++ ", 4) != 0 || 542 (cp1 = strchr (line1, '\t')) == NULL || 543 (cp2 = strchr (line2, '\t')) == NULL) 544 { 545 error (0, 0, "invalid unidiff header for %s", rcs); 546 ret = 1; 547 (void) fclose (fp); 548 goto out; 549 } 550 } 551 if (CVSroot_directory != NULL) 552 (void) sprintf (strippath, "%s/", CVSroot_directory); 553 else 554 (void) strcpy (strippath, REPOS_STRIP); 555 if (strncmp (rcs, strippath, strlen (strippath)) == 0) 556 rcs += strlen (strippath); 557 if (vers_tag != NULL) 558 { 559 (void) sprintf (file1, "%s:%s", finfo->fullname, vers_tag); 560 } 561 else 562 { 563 (void) strcpy (file1, DEVNULL); 564 } 565 (void) sprintf (file2, "%s:%s", finfo->fullname, 566 vers_head ? vers_head : "removed"); 567 568 /* Note that this prints "diff" not DIFF. The format of a diff 569 does not depend on the name of the program which happens to 570 have produced it. */ 571 if (unidiff) 572 { 573 (void) printf ("diff -u %s %s\n", file1, file2); 574 (void) printf ("--- %s%s+++ ", file1, cp1); 575 } 576 else 577 { 578 (void) printf ("diff -c %s %s\n", file1, file2); 579 (void) printf ("*** %s%s--- ", file1, cp1); 580 } 581 582 (void) printf ("%s%s", finfo->fullname, cp2); 583 /* spew the rest of the diff out */ 584 while (getline (&line1, &line1_chars_allocated, fp) >= 0) 585 (void) fputs (line1, stdout); 586 (void) fclose (fp); 587 break; 588 default: 589 error (0, 0, "diff failed for %s", finfo->fullname); 590 } 591 out: 592 if (line1) 593 free (line1); 594 if (line2) 595 free (line2); 596 /* FIXME: should be checking for errors. */ 597 (void) CVS_UNLINK (tmpfile1); 598 (void) CVS_UNLINK (tmpfile2); 599 (void) CVS_UNLINK (tmpfile3); 600 free (tmpfile1); 601 free (tmpfile2); 602 free (tmpfile3); 603 tmpfile1 = tmpfile2 = tmpfile3 = NULL; 604 return (ret); 605 } 606 607 /* 608 * Print a warm fuzzy message 609 */ 610 /* ARGSUSED */ 611 static Dtype 612 patch_dirproc (callerdat, dir, repos, update_dir, entries) 613 void *callerdat; 614 char *dir; 615 char *repos; 616 char *update_dir; 617 List *entries; 618 { 619 if (!quiet) 620 error (0, 0, "Diffing %s", update_dir); 621 return (R_PROCESS); 622 } 623 624 /* 625 * Clean up temporary files 626 */ 627 static RETSIGTYPE 628 patch_cleanup () 629 { 630 if (tmpfile1 != NULL) 631 { 632 (void) unlink_file (tmpfile1); 633 free (tmpfile1); 634 } 635 if (tmpfile2 != NULL) 636 { 637 (void) unlink_file (tmpfile2); 638 free (tmpfile2); 639 } 640 if (tmpfile3 != NULL) 641 { 642 (void) unlink_file (tmpfile3); 643 free (tmpfile3); 644 } 645 tmpfile1 = tmpfile2 = tmpfile3 = NULL; 646 } 647