1 /* $OpenBSD: diff.c,v 1.160 2011/04/20 18:33:13 nicm Exp $ */ 2 /* 3 * Copyright (c) 2008 Tobias Stoeckmann <tobias@openbsd.org> 4 * Copyright (c) 2006 Joris Vink <joris@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/stat.h> 20 #include <sys/time.h> 21 22 #include <errno.h> 23 #include <fcntl.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <time.h> 27 #include <unistd.h> 28 29 #include "cvs.h" 30 #include "diff.h" 31 #include "remote.h" 32 33 void cvs_diff_local(struct cvs_file *); 34 35 static int dflags = 0; 36 static int Nflag = 0; 37 static int force_head = 0; 38 static char *koptstr; 39 static char *rev1 = NULL; 40 static char *rev2 = NULL; 41 static time_t date1 = -1; 42 static time_t date2 = -1; 43 static char *dateflag1 = NULL; 44 static char *dateflag2 = NULL; 45 46 struct cvs_cmd cvs_cmd_diff = { 47 CVS_OP_DIFF, CVS_USE_WDIR, "diff", 48 { "di", "dif" }, 49 "Show differences between revisions", 50 "[-abcdilNnpRuw] [[-D date] [-r rev] [-D date2 | -r rev2]] " 51 "[-k mode] [file ...]", 52 "abcfC:dD:ik:lNnpr:RuU:w", 53 NULL, 54 cvs_diff 55 }; 56 57 struct cvs_cmd cvs_cmd_rdiff = { 58 CVS_OP_RDIFF, 0, "rdiff", 59 { "patch", "pa" }, 60 "Show differences between revisions", 61 "[-flR] [-c | -u] [-s | -t] [-V ver] -D date | -r rev\n" 62 "[-D date2 | -r rev2] [-k mode] module ...", 63 "cfD:k:lr:RuV:", 64 NULL, 65 cvs_diff 66 }; 67 68 int 69 cvs_diff(int argc, char **argv) 70 { 71 int ch, flags; 72 char *arg = "."; 73 const char *errstr; 74 struct cvs_recursion cr; 75 76 flags = CR_RECURSE_DIRS; 77 strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff", 78 sizeof(diffargs)); 79 80 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ? 81 cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) { 82 switch (ch) { 83 case 'a': 84 strlcat(diffargs, " -a", sizeof(diffargs)); 85 dflags |= D_FORCEASCII; 86 break; 87 case 'b': 88 strlcat(diffargs, " -b", sizeof(diffargs)); 89 dflags |= D_FOLDBLANKS; 90 break; 91 case 'c': 92 strlcat(diffargs, " -c", sizeof(diffargs)); 93 diff_format = D_CONTEXT; 94 break; 95 case 'C': 96 diff_context = strtonum(optarg, 0, INT_MAX, &errstr); 97 if (errstr != NULL) 98 fatal("context lines %s: %s", errstr, optarg); 99 strlcat(diffargs, " -C ", sizeof(diffargs)); 100 strlcat(diffargs, optarg, sizeof(diffargs)); 101 diff_format = D_CONTEXT; 102 break; 103 case 'd': 104 strlcat(diffargs, " -d", sizeof(diffargs)); 105 dflags |= D_MINIMAL; 106 break; 107 case 'D': 108 if (date1 == -1 && rev1 == NULL) { 109 if ((date1 = date_parse(optarg)) == -1) 110 fatal("invalid date: %s", optarg); 111 dateflag1 = optarg; 112 } else if (date2 == -1 && rev2 == NULL) { 113 if ((date2 = date_parse(optarg)) == -1) 114 fatal("invalid date: %s", optarg); 115 dateflag2 = optarg; 116 } else { 117 fatal("no more than 2 revisions/dates can" 118 " be specified"); 119 } 120 break; 121 case 'f': 122 force_head = 1; 123 break; 124 case 'i': 125 strlcat(diffargs, " -i", sizeof(diffargs)); 126 dflags |= D_IGNORECASE; 127 break; 128 case 'k': 129 koptstr = optarg; 130 kflag = rcs_kflag_get(koptstr); 131 if (RCS_KWEXP_INVAL(kflag)) { 132 cvs_log(LP_ERR, 133 "invalid RCS keyword expansion mode"); 134 fatal("%s", cvs_cmdop == CVS_OP_DIFF ? 135 cvs_cmd_diff.cmd_synopsis : 136 cvs_cmd_rdiff.cmd_synopsis); 137 } 138 break; 139 case 'l': 140 flags &= ~CR_RECURSE_DIRS; 141 break; 142 case 'n': 143 strlcat(diffargs, " -n", sizeof(diffargs)); 144 diff_format = D_RCSDIFF; 145 break; 146 case 'N': 147 strlcat(diffargs, " -N", sizeof(diffargs)); 148 Nflag = 1; 149 break; 150 case 'p': 151 strlcat(diffargs, " -p", sizeof(diffargs)); 152 dflags |= D_PROTOTYPE; 153 break; 154 case 'R': 155 flags |= CR_RECURSE_DIRS; 156 break; 157 case 'r': 158 if (date1 == -1 && rev1 == NULL) { 159 rev1 = optarg; 160 } else if (date2 == -1 && rev2 == NULL) { 161 rev2 = optarg; 162 } else { 163 fatal("no more than 2 revisions/dates can" 164 " be specified"); 165 } 166 break; 167 case 't': 168 strlcat(diffargs, " -t", sizeof(diffargs)); 169 dflags |= D_EXPANDTABS; 170 break; 171 case 'u': 172 strlcat(diffargs, " -u", sizeof(diffargs)); 173 diff_format = D_UNIFIED; 174 break; 175 case 'U': 176 diff_context = strtonum(optarg, 0, INT_MAX, &errstr); 177 if (errstr != NULL) 178 fatal("context lines %s: %s", errstr, optarg); 179 strlcat(diffargs, " -U ", sizeof(diffargs)); 180 strlcat(diffargs, optarg, sizeof(diffargs)); 181 diff_format = D_UNIFIED; 182 break; 183 case 'V': 184 fatal("the -V option is obsolete " 185 "and should not be used"); 186 case 'w': 187 strlcat(diffargs, " -w", sizeof(diffargs)); 188 dflags |= D_IGNOREBLANKS; 189 break; 190 default: 191 fatal("%s", cvs_cmdop == CVS_OP_DIFF ? 192 cvs_cmd_diff.cmd_synopsis : 193 cvs_cmd_rdiff.cmd_synopsis); 194 } 195 } 196 197 argc -= optind; 198 argv += optind; 199 200 cr.enterdir = NULL; 201 cr.leavedir = NULL; 202 203 if (cvs_cmdop == CVS_OP_RDIFF) { 204 if (rev1 == NULL && rev2 == NULL && dateflag1 == NULL && 205 dateflag2 == NULL) 206 fatal("must specify at least one revision/date!"); 207 208 if (!argc) 209 fatal("%s", cvs_cmd_rdiff.cmd_synopsis); 210 211 if (!diff_format) { 212 strlcat(diffargs, " -c", sizeof(diffargs)); 213 diff_format = D_CONTEXT; 214 } 215 216 flags |= CR_REPO; 217 } 218 219 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 220 cvs_client_connect_to_server(); 221 cr.fileproc = cvs_client_sendfile; 222 223 if (!(flags & CR_RECURSE_DIRS)) 224 cvs_client_send_request("Argument -l"); 225 226 if (kflag) 227 cvs_client_send_request("Argument -k%s", koptstr); 228 229 switch (diff_format) { 230 case D_CONTEXT: 231 if (cvs_cmdop == CVS_OP_RDIFF) 232 cvs_client_send_request("Argument -c"); 233 else { 234 cvs_client_send_request("Argument -C %d", 235 diff_context); 236 } 237 break; 238 case D_RCSDIFF: 239 cvs_client_send_request("Argument -n"); 240 break; 241 case D_UNIFIED: 242 if (cvs_cmdop == CVS_OP_RDIFF || diff_context == 3) 243 cvs_client_send_request("Argument -u"); 244 else { 245 cvs_client_send_request("Argument -U %d", 246 diff_context); 247 } 248 break; 249 default: 250 break; 251 } 252 253 if (Nflag == 1) 254 cvs_client_send_request("Argument -N"); 255 256 if (dflags & D_PROTOTYPE) 257 cvs_client_send_request("Argument -p"); 258 259 if (rev1 != NULL) 260 cvs_client_send_request("Argument -r%s", rev1); 261 if (rev2 != NULL) 262 cvs_client_send_request("Argument -r%s", rev2); 263 264 if (dateflag1 != NULL) 265 cvs_client_send_request("Argument -D%s", dateflag1); 266 if (dateflag2 != NULL) 267 cvs_client_send_request("Argument -D%s", dateflag2); 268 } else { 269 if (cvs_cmdop == CVS_OP_RDIFF && 270 chdir(current_cvsroot->cr_dir) == -1) 271 fatal("cvs_diff: %s", strerror(errno)); 272 273 cr.fileproc = cvs_diff_local; 274 } 275 276 cr.flags = flags; 277 278 diff_rev1 = diff_rev2 = NULL; 279 280 if (cvs_cmdop == CVS_OP_DIFF || 281 current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 282 if (argc > 0) 283 cvs_file_run(argc, argv, &cr); 284 else 285 cvs_file_run(1, &arg, &cr); 286 } 287 288 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 289 cvs_client_send_files(argv, argc); 290 cvs_client_senddir("."); 291 292 cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ? 293 "rdiff" : "diff"); 294 295 cvs_client_get_responses(); 296 } 297 298 return (0); 299 } 300 301 void 302 cvs_diff_local(struct cvs_file *cf) 303 { 304 BUF *b1; 305 int fd1, fd2; 306 struct stat st; 307 struct timeval tv[2], tv2[2]; 308 struct tm datetm; 309 char rbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ], *p1, *p2; 310 311 b1 = NULL; 312 fd1 = fd2 = -1; 313 p1 = p2 = NULL; 314 315 cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path); 316 317 if (cf->file_type == CVS_DIR) { 318 if (verbosity > 1) 319 cvs_log(LP_ERR, "Diffing inside %s", cf->file_path); 320 return; 321 } 322 323 cvs_file_classify(cf, cvs_directory_tag); 324 325 if (cvs_cmdop == CVS_OP_DIFF) { 326 if (cf->file_ent == NULL) { 327 cvs_log(LP_ERR, "I know nothing about %s", 328 cf->file_path); 329 return; 330 } 331 332 switch (cf->file_ent->ce_status) { 333 case CVS_ENT_ADDED: 334 if (Nflag == 0) { 335 cvs_log(LP_ERR, "%s is a new entry, no " 336 "comparison available", cf->file_path); 337 return; 338 } 339 if (!(cf->file_flags & FILE_ON_DISK)) { 340 cvs_log(LP_ERR, "cannot find %s", 341 cf->file_path); 342 return; 343 } 344 break; 345 case CVS_ENT_REMOVED: 346 if (Nflag == 0) { 347 cvs_log(LP_ERR, "%s was removed, no " 348 "comparison available", cf->file_path); 349 return; 350 } 351 if (cf->file_rcs == NULL) { 352 cvs_log(LP_ERR, "cannot find RCS file for %s", 353 cf->file_path); 354 return; 355 } 356 break; 357 default: 358 if (!(cf->file_flags & FILE_ON_DISK)) { 359 cvs_printf("? %s\n", cf->file_path); 360 return; 361 } 362 363 if (cf->file_rcs == NULL) { 364 cvs_log(LP_ERR, "cannot find RCS file for %s", 365 cf->file_path); 366 return; 367 } 368 break; 369 } 370 } 371 372 if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL && 373 date1 == -1 && date2 == -1) 374 return; 375 376 if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) { 377 cvs_log(LP_ERR, "no head revision in RCS file for %s\n", 378 cf->file_path); 379 return; 380 } 381 382 if (kflag && cf->file_rcs != NULL) 383 rcs_kwexp_set(cf->file_rcs, kflag); 384 385 if (cf->file_rcs == NULL) 386 diff_rev1 = NULL; 387 else if (rev1 != NULL || date1 != -1) { 388 cvs_specified_date = date1; 389 diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs); 390 if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) { 391 if (rev1 != NULL) { 392 cvs_log(LP_ERR, "tag %s not in file %s", rev1, 393 cf->file_path); 394 goto cleanup; 395 } else if (Nflag) { 396 diff_rev1 = NULL; 397 } else { 398 gmtime_r(&cvs_specified_date, &datetm); 399 strftime(tbuf, sizeof(tbuf), 400 "%Y.%m.%d.%H.%M.%S", &datetm); 401 cvs_log(LP_ERR, "no revision for date %s in " 402 "file %s", tbuf, cf->file_path); 403 goto cleanup; 404 } 405 } else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF && 406 force_head) { 407 /* -f is not allowed for unknown symbols */ 408 if ((diff_rev1 = rcsnum_parse(rev1)) == NULL) 409 fatal("no such tag %s", rev1); 410 rcsnum_free(diff_rev1); 411 412 diff_rev1 = cf->file_rcs->rf_head; 413 } 414 cvs_specified_date = -1; 415 } else if (cvs_cmdop == CVS_OP_DIFF) { 416 if (cf->file_ent->ce_status == CVS_ENT_ADDED) 417 diff_rev1 = NULL; 418 else 419 diff_rev1 = cf->file_ent->ce_rev; 420 } 421 422 if (cf->file_rcs == NULL) 423 diff_rev2 = NULL; 424 else if (rev2 != NULL || date2 != -1) { 425 cvs_specified_date = date2; 426 diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs); 427 if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) { 428 if (rev2 != NULL) { 429 cvs_log(LP_ERR, "tag %s not in file %s", rev2, 430 cf->file_path); 431 goto cleanup; 432 } else if (Nflag) { 433 diff_rev2 = NULL; 434 } else { 435 gmtime_r(&cvs_specified_date, &datetm); 436 strftime(tbuf, sizeof(tbuf), 437 "%Y.%m.%d.%H.%M.%S", &datetm); 438 cvs_log(LP_ERR, "no revision for date %s in " 439 "file %s", tbuf, cf->file_path); 440 goto cleanup; 441 } 442 } else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF && 443 force_head) { 444 /* -f is not allowed for unknown symbols */ 445 if ((diff_rev2 = rcsnum_parse(rev2)) == NULL) 446 fatal("no such tag %s", rev2); 447 rcsnum_free(diff_rev2); 448 449 diff_rev2 = cf->file_rcs->rf_head; 450 } 451 cvs_specified_date = -1; 452 } else if (cvs_cmdop == CVS_OP_RDIFF) 453 diff_rev2 = cf->file_rcs->rf_head; 454 else if (cf->file_ent->ce_status == CVS_ENT_REMOVED) 455 diff_rev2 = NULL; 456 457 if (diff_rev1 != NULL && diff_rev2 != NULL && 458 rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0) 459 goto cleanup; 460 461 switch (cvs_cmdop) { 462 case CVS_OP_DIFF: 463 if (cf->file_status == FILE_UPTODATE) { 464 if (diff_rev2 == NULL && 465 !rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0)) 466 goto cleanup; 467 } 468 break; 469 case CVS_OP_RDIFF: 470 if (diff_rev1 == NULL && diff_rev2 == NULL) 471 goto cleanup; 472 break; 473 } 474 475 cvs_printf("Index: %s\n", cf->file_path); 476 if (cvs_cmdop == CVS_OP_DIFF) 477 cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV, 478 cf->file_rcs != NULL ? cf->file_rpath : cf->file_path); 479 480 if (diff_rev1 != NULL) { 481 if (cvs_cmdop == CVS_OP_DIFF && diff_rev1 != NULL) { 482 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 483 cvs_printf("retrieving revision %s\n", rbuf); 484 } 485 486 tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev1); 487 tv[0].tv_usec = 0; 488 tv[1] = tv[0]; 489 490 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 491 fd1 = rcs_rev_write_stmp(cf->file_rcs, diff_rev1, p1, 0); 492 if (futimes(fd1, tv) == -1) 493 fatal("cvs_diff_local: utimes failed"); 494 } 495 496 if (diff_rev2 != NULL) { 497 if (cvs_cmdop == CVS_OP_DIFF && rev2 != NULL) { 498 (void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf)); 499 cvs_printf("retrieving revision %s\n", rbuf); 500 } 501 502 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2); 503 tv2[0].tv_usec = 0; 504 tv2[1] = tv2[0]; 505 506 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 507 fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0); 508 if (futimes(fd2, tv2) == -1) 509 fatal("cvs_diff_local: utimes failed"); 510 } else if (cvs_cmdop == CVS_OP_DIFF && 511 (cf->file_flags & FILE_ON_DISK) && 512 cf->file_ent->ce_status != CVS_ENT_REMOVED) { 513 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 514 if (cvs_server_active == 1 && cf->fd == -1) { 515 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, 516 cf->file_ent->ce_rev); 517 tv2[0].tv_usec = 0; 518 tv2[1] = tv2[0]; 519 520 fd2 = rcs_rev_write_stmp(cf->file_rcs, 521 cf->file_ent->ce_rev, p2, 0); 522 if (futimes(fd2, tv2) == -1) 523 fatal("cvs_diff_local: futimes failed"); 524 } else { 525 if (fstat(cf->fd, &st) == -1) 526 fatal("fstat failed %s", strerror(errno)); 527 b1 = buf_load_fd(cf->fd); 528 529 tv2[0].tv_sec = st.st_mtime; 530 tv2[0].tv_usec = 0; 531 tv2[1] = tv2[0]; 532 533 fd2 = buf_write_stmp(b1, p2, tv2); 534 buf_free(b1); 535 } 536 } 537 538 switch (cvs_cmdop) { 539 case CVS_OP_DIFF: 540 cvs_printf("%s", diffargs); 541 542 if (rev1 != NULL && diff_rev1 != NULL) { 543 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 544 cvs_printf(" -r%s", rbuf); 545 546 if (rev2 != NULL && diff_rev2 != NULL) { 547 (void)rcsnum_tostr(diff_rev2, rbuf, 548 sizeof(rbuf)); 549 cvs_printf(" -r%s", rbuf); 550 } 551 } 552 553 if (diff_rev2 == NULL) 554 cvs_printf(" %s", cf->file_path); 555 cvs_printf("\n"); 556 break; 557 case CVS_OP_RDIFF: 558 cvs_printf("diff "); 559 switch (diff_format) { 560 case D_CONTEXT: 561 cvs_printf("-c "); 562 break; 563 case D_RCSDIFF: 564 cvs_printf("-n "); 565 break; 566 case D_UNIFIED: 567 cvs_printf("-u "); 568 break; 569 default: 570 break; 571 } 572 if (diff_rev1 == NULL) { 573 cvs_printf("%s ", CVS_PATH_DEVNULL); 574 } else { 575 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 576 cvs_printf("%s:%s ", cf->file_path, rbuf); 577 } 578 579 if (diff_rev2 == NULL) { 580 cvs_printf("%s:removed\n", cf->file_path); 581 } else { 582 (void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 : 583 cf->file_rcs->rf_head, rbuf, sizeof(rbuf)); 584 cvs_printf("%s:%s\n", cf->file_path, rbuf); 585 } 586 break; 587 } 588 589 if (fd1 == -1) { 590 if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1) 591 fatal("cannot open %s", CVS_PATH_DEVNULL); 592 } 593 if (fd2 == -1) { 594 if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1) 595 fatal("cannot open %s", CVS_PATH_DEVNULL); 596 } 597 598 if (diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL, 599 p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL, 600 dflags) == D_ERROR) 601 fatal("cvs_diff_local: failed to get RCS patch"); 602 603 close(fd1); 604 close(fd2); 605 606 worklist_run(&temp_files, worklist_unlink); 607 608 if (p1 != NULL) 609 xfree(p1); 610 if (p2 != NULL) 611 xfree(p2); 612 613 cleanup: 614 if (diff_rev1 != NULL && 615 (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) && 616 (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev)) 617 xfree(diff_rev1); 618 diff_rev1 = NULL; 619 620 if (diff_rev2 != NULL && 621 (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head)) 622 xfree(diff_rev2); 623 diff_rev2 = NULL; 624 } 625