1 /* $OpenBSD: diff.c,v 1.155 2009/06/07 08:39:13 ray 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 "abcfdD:ik:lNnpr:Ruw", 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 struct cvs_recursion cr; 74 75 flags = CR_RECURSE_DIRS; 76 strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff", 77 sizeof(diffargs)); 78 79 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ? 80 cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) { 81 switch (ch) { 82 case 'a': 83 strlcat(diffargs, " -a", sizeof(diffargs)); 84 dflags |= D_FORCEASCII; 85 break; 86 case 'b': 87 strlcat(diffargs, " -b", sizeof(diffargs)); 88 dflags |= D_FOLDBLANKS; 89 break; 90 case 'c': 91 strlcat(diffargs, " -c", sizeof(diffargs)); 92 diff_format = D_CONTEXT; 93 break; 94 case 'd': 95 strlcat(diffargs, " -d", sizeof(diffargs)); 96 dflags |= D_MINIMAL; 97 break; 98 case 'D': 99 if (date1 == -1 && rev1 == NULL) { 100 date1 = cvs_date_parse(optarg); 101 dateflag1 = optarg; 102 } else if (date2 == -1 && rev2 == NULL) { 103 date2 = cvs_date_parse(optarg); 104 dateflag2 = optarg; 105 } else { 106 fatal("no more than 2 revisions/dates can" 107 " be specified"); 108 } 109 break; 110 case 'f': 111 force_head = 1; 112 break; 113 case 'i': 114 strlcat(diffargs, " -i", sizeof(diffargs)); 115 dflags |= D_IGNORECASE; 116 break; 117 case 'k': 118 koptstr = optarg; 119 kflag = rcs_kflag_get(koptstr); 120 if (RCS_KWEXP_INVAL(kflag)) { 121 cvs_log(LP_ERR, 122 "invalid RCS keyword expansion mode"); 123 fatal("%s", cvs_cmdop == CVS_OP_DIFF ? 124 cvs_cmd_diff.cmd_synopsis : 125 cvs_cmd_rdiff.cmd_synopsis); 126 } 127 break; 128 case 'l': 129 flags &= ~CR_RECURSE_DIRS; 130 break; 131 case 'n': 132 strlcat(diffargs, " -n", sizeof(diffargs)); 133 diff_format = D_RCSDIFF; 134 break; 135 case 'N': 136 strlcat(diffargs, " -N", sizeof(diffargs)); 137 Nflag = 1; 138 break; 139 case 'p': 140 strlcat(diffargs, " -p", sizeof(diffargs)); 141 dflags |= D_PROTOTYPE; 142 break; 143 case 'R': 144 flags |= CR_RECURSE_DIRS; 145 break; 146 case 'r': 147 if (date1 == -1 && rev1 == NULL) { 148 rev1 = optarg; 149 } else if (date2 == -1 && rev2 == NULL) { 150 rev2 = optarg; 151 } else { 152 fatal("no more than 2 revisions/dates can" 153 " be specified"); 154 } 155 break; 156 case 't': 157 strlcat(diffargs, " -t", sizeof(diffargs)); 158 dflags |= D_EXPANDTABS; 159 break; 160 case 'u': 161 strlcat(diffargs, " -u", sizeof(diffargs)); 162 diff_format = D_UNIFIED; 163 break; 164 case 'V': 165 fatal("the -V option is obsolete " 166 "and should not be used"); 167 case 'w': 168 strlcat(diffargs, " -w", sizeof(diffargs)); 169 dflags |= D_IGNOREBLANKS; 170 break; 171 default: 172 fatal("%s", cvs_cmdop == CVS_OP_DIFF ? 173 cvs_cmd_diff.cmd_synopsis : 174 cvs_cmd_rdiff.cmd_synopsis); 175 } 176 } 177 178 argc -= optind; 179 argv += optind; 180 181 cr.enterdir = NULL; 182 cr.leavedir = NULL; 183 184 if (cvs_cmdop == CVS_OP_RDIFF) { 185 if (rev1 == NULL && rev2 == NULL && dateflag1 == NULL && 186 dateflag2 == NULL) 187 fatal("must specify at least one revision/date!"); 188 189 if (!argc) 190 fatal("%s", cvs_cmd_rdiff.cmd_synopsis); 191 192 if (!diff_format) { 193 strlcat(diffargs, " -c", sizeof(diffargs)); 194 diff_format = D_CONTEXT; 195 } 196 197 flags |= CR_REPO; 198 } 199 200 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 201 cvs_client_connect_to_server(); 202 cr.fileproc = cvs_client_sendfile; 203 204 if (!(flags & CR_RECURSE_DIRS)) 205 cvs_client_send_request("Argument -l"); 206 207 if (kflag) 208 cvs_client_send_request("Argument -k%s", koptstr); 209 210 switch (diff_format) { 211 case D_CONTEXT: 212 cvs_client_send_request("Argument -c"); 213 break; 214 case D_RCSDIFF: 215 cvs_client_send_request("Argument -n"); 216 break; 217 case D_UNIFIED: 218 cvs_client_send_request("Argument -u"); 219 break; 220 default: 221 break; 222 } 223 224 if (Nflag == 1) 225 cvs_client_send_request("Argument -N"); 226 227 if (dflags & D_PROTOTYPE) 228 cvs_client_send_request("Argument -p"); 229 230 if (rev1 != NULL) 231 cvs_client_send_request("Argument -r%s", rev1); 232 if (rev2 != NULL) 233 cvs_client_send_request("Argument -r%s", rev2); 234 235 if (dateflag1 != NULL) 236 cvs_client_send_request("Argument -D%s", dateflag1); 237 if (dateflag2 != NULL) 238 cvs_client_send_request("Argument -D%s", dateflag2); 239 } else { 240 if (cvs_cmdop == CVS_OP_RDIFF && 241 chdir(current_cvsroot->cr_dir) == -1) 242 fatal("cvs_diff: %s", strerror(errno)); 243 244 cr.fileproc = cvs_diff_local; 245 } 246 247 cr.flags = flags; 248 249 diff_rev1 = diff_rev2 = NULL; 250 251 if (cvs_cmdop == CVS_OP_DIFF || 252 current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 253 if (argc > 0) 254 cvs_file_run(argc, argv, &cr); 255 else 256 cvs_file_run(1, &arg, &cr); 257 } 258 259 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 260 cvs_client_send_files(argv, argc); 261 cvs_client_senddir("."); 262 263 cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ? 264 "rdiff" : "diff"); 265 266 cvs_client_get_responses(); 267 } 268 269 return (0); 270 } 271 272 void 273 cvs_diff_local(struct cvs_file *cf) 274 { 275 BUF *b1; 276 int fd1, fd2; 277 struct stat st; 278 struct timeval tv[2], tv2[2]; 279 struct tm datetm; 280 char rbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ], *p1, *p2; 281 282 b1 = NULL; 283 fd1 = fd2 = -1; 284 p1 = p2 = NULL; 285 286 cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path); 287 288 if (cf->file_type == CVS_DIR) { 289 if (verbosity > 1) 290 cvs_log(LP_ERR, "Diffing inside %s", cf->file_path); 291 return; 292 } 293 294 cvs_file_classify(cf, cvs_directory_tag); 295 296 if (cvs_cmdop == CVS_OP_DIFF) { 297 if (cf->file_ent == NULL) { 298 cvs_log(LP_ERR, "I know nothing about %s", 299 cf->file_path); 300 return; 301 } 302 303 switch (cf->file_ent->ce_status) { 304 case CVS_ENT_ADDED: 305 if (Nflag == 0) { 306 cvs_log(LP_ERR, "%s is a new entry, no " 307 "comparison available", cf->file_path); 308 return; 309 } 310 if (!(cf->file_flags & FILE_ON_DISK)) { 311 cvs_log(LP_ERR, "cannot find %s", 312 cf->file_path); 313 return; 314 } 315 break; 316 case CVS_ENT_REMOVED: 317 if (Nflag == 0) { 318 cvs_log(LP_ERR, "%s was removed, no " 319 "comparison available", cf->file_path); 320 return; 321 } 322 if (cf->file_rcs == NULL) { 323 cvs_log(LP_ERR, "cannot find RCS file for %s", 324 cf->file_path); 325 return; 326 } 327 break; 328 default: 329 if (!(cf->file_flags & FILE_ON_DISK)) { 330 cvs_printf("? %s\n", cf->file_path); 331 return; 332 } 333 334 if (cf->file_rcs == NULL) { 335 cvs_log(LP_ERR, "cannot find RCS file for %s", 336 cf->file_path); 337 return; 338 } 339 break; 340 } 341 } 342 343 if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL && 344 date1 == -1 && date2 == -1) 345 return; 346 347 if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) { 348 cvs_log(LP_ERR, "no head revision in RCS file for %s\n", 349 cf->file_path); 350 return; 351 } 352 353 if (kflag && cf->file_rcs != NULL) 354 rcs_kwexp_set(cf->file_rcs, kflag); 355 356 if (cf->file_rcs == NULL) 357 diff_rev1 = NULL; 358 else if (rev1 != NULL || date1 != -1) { 359 cvs_specified_date = date1; 360 diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs); 361 if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) { 362 if (rev1 != NULL) { 363 cvs_log(LP_ERR, "tag %s not in file %s", rev1, 364 cf->file_path); 365 goto cleanup; 366 } else if (Nflag) { 367 diff_rev1 = NULL; 368 } else { 369 gmtime_r(&cvs_specified_date, &datetm); 370 strftime(tbuf, sizeof(tbuf), 371 "%Y.%m.%d.%H.%M.%S", &datetm); 372 cvs_log(LP_ERR, "no revision for date %s in " 373 "file %s", tbuf, cf->file_path); 374 goto cleanup; 375 } 376 } else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF && 377 force_head) { 378 /* -f is not allowed for unknown symbols */ 379 if ((diff_rev1 = rcsnum_parse(rev1)) == NULL) 380 fatal("no such tag %s", rev1); 381 rcsnum_free(diff_rev1); 382 383 diff_rev1 = cf->file_rcs->rf_head; 384 } 385 cvs_specified_date = -1; 386 } else if (cvs_cmdop == CVS_OP_DIFF) { 387 if (cf->file_ent->ce_status == CVS_ENT_ADDED) 388 diff_rev1 = NULL; 389 else 390 diff_rev1 = cf->file_ent->ce_rev; 391 } 392 393 if (cf->file_rcs == NULL) 394 diff_rev2 = NULL; 395 else if (rev2 != NULL || date2 != -1) { 396 cvs_specified_date = date2; 397 diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs); 398 if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) { 399 if (rev2 != NULL) { 400 cvs_log(LP_ERR, "tag %s not in file %s", rev2, 401 cf->file_path); 402 goto cleanup; 403 } else if (Nflag) { 404 diff_rev2 = NULL; 405 } else { 406 gmtime_r(&cvs_specified_date, &datetm); 407 strftime(tbuf, sizeof(tbuf), 408 "%Y.%m.%d.%H.%M.%S", &datetm); 409 cvs_log(LP_ERR, "no revision for date %s in " 410 "file %s", tbuf, cf->file_path); 411 goto cleanup; 412 } 413 } else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF && 414 force_head) { 415 /* -f is not allowed for unknown symbols */ 416 if ((diff_rev2 = rcsnum_parse(rev2)) == NULL) 417 fatal("no such tag %s", rev2); 418 rcsnum_free(diff_rev2); 419 420 diff_rev2 = cf->file_rcs->rf_head; 421 } 422 cvs_specified_date = -1; 423 } else if (cvs_cmdop == CVS_OP_RDIFF) 424 diff_rev2 = cf->file_rcs->rf_head; 425 else if (cf->file_ent->ce_status == CVS_ENT_REMOVED) 426 diff_rev2 = NULL; 427 428 if (diff_rev1 != NULL && diff_rev2 != NULL && 429 rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0) 430 goto cleanup; 431 432 switch (cvs_cmdop) { 433 case CVS_OP_DIFF: 434 if (cf->file_status == FILE_UPTODATE) { 435 if (diff_rev2 == NULL && 436 !rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0)) 437 goto cleanup; 438 } 439 break; 440 case CVS_OP_RDIFF: 441 if (diff_rev1 == NULL && diff_rev2 == NULL) 442 goto cleanup; 443 break; 444 } 445 446 cvs_printf("Index: %s\n", cf->file_path); 447 if (cvs_cmdop == CVS_OP_DIFF) 448 cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV, 449 cf->file_rcs != NULL ? cf->file_rpath : cf->file_path); 450 451 if (diff_rev1 != NULL) { 452 if (cvs_cmdop == CVS_OP_DIFF && diff_rev1 != NULL) { 453 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 454 cvs_printf("retrieving revision %s\n", rbuf); 455 } 456 457 tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev1); 458 tv[0].tv_usec = 0; 459 tv[1] = tv[0]; 460 461 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 462 fd1 = rcs_rev_write_stmp(cf->file_rcs, diff_rev1, p1, 0); 463 if (futimes(fd1, tv) == -1) 464 fatal("cvs_diff_local: utimes failed"); 465 } 466 467 if (diff_rev2 != NULL) { 468 if (cvs_cmdop == CVS_OP_DIFF && rev2 != NULL) { 469 (void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf)); 470 cvs_printf("retrieving revision %s\n", rbuf); 471 } 472 473 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2); 474 tv2[0].tv_usec = 0; 475 tv2[1] = tv2[0]; 476 477 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 478 fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0); 479 if (futimes(fd2, tv2) == -1) 480 fatal("cvs_diff_local: utimes failed"); 481 } else if (cvs_cmdop == CVS_OP_DIFF && 482 (cf->file_flags & FILE_ON_DISK) && 483 cf->file_ent->ce_status != CVS_ENT_REMOVED) { 484 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 485 if (cvs_server_active == 1 && cf->fd == -1) { 486 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, 487 cf->file_ent->ce_rev); 488 tv2[0].tv_usec = 0; 489 tv2[1] = tv2[0]; 490 491 fd2 = rcs_rev_write_stmp(cf->file_rcs, 492 cf->file_ent->ce_rev, p2, 0); 493 if (futimes(fd2, tv2) == -1) 494 fatal("cvs_diff_local: futimes failed"); 495 } else { 496 if (fstat(cf->fd, &st) == -1) 497 fatal("fstat failed %s", strerror(errno)); 498 b1 = cvs_buf_load_fd(cf->fd); 499 500 tv2[0].tv_sec = st.st_mtime; 501 tv2[0].tv_usec = 0; 502 tv2[1] = tv2[0]; 503 504 fd2 = cvs_buf_write_stmp(b1, p2, tv2); 505 cvs_buf_free(b1); 506 } 507 } 508 509 switch (cvs_cmdop) { 510 case CVS_OP_DIFF: 511 cvs_printf("%s", diffargs); 512 513 if (rev1 != NULL && diff_rev1 != NULL) { 514 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 515 cvs_printf(" -r%s", rbuf); 516 517 if (rev2 != NULL && diff_rev2 != NULL) { 518 (void)rcsnum_tostr(diff_rev2, rbuf, 519 sizeof(rbuf)); 520 cvs_printf(" -r%s", rbuf); 521 } 522 } 523 524 if (diff_rev2 == NULL) 525 cvs_printf(" %s", cf->file_path); 526 cvs_printf("\n"); 527 break; 528 case CVS_OP_RDIFF: 529 cvs_printf("diff "); 530 switch (diff_format) { 531 case D_CONTEXT: 532 cvs_printf("-c "); 533 break; 534 case D_RCSDIFF: 535 cvs_printf("-n "); 536 break; 537 case D_UNIFIED: 538 cvs_printf("-u "); 539 break; 540 default: 541 break; 542 } 543 if (diff_rev1 == NULL) { 544 cvs_printf("%s ", CVS_PATH_DEVNULL); 545 } else { 546 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 547 cvs_printf("%s:%s ", cf->file_path, rbuf); 548 } 549 550 if (diff_rev2 == NULL) { 551 cvs_printf("%s:removed\n", cf->file_path); 552 } else { 553 (void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 : 554 cf->file_rcs->rf_head, rbuf, sizeof(rbuf)); 555 cvs_printf("%s:%s\n", cf->file_path, rbuf); 556 } 557 break; 558 } 559 560 if (fd1 == -1) { 561 if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1) 562 fatal("cannot open %s", CVS_PATH_DEVNULL); 563 } 564 if (fd2 == -1) { 565 if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1) 566 fatal("cannot open %s", CVS_PATH_DEVNULL); 567 } 568 569 if (diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL, 570 p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL, 571 dflags) == D_ERROR) 572 fatal("cvs_diff_local: failed to get RCS patch"); 573 574 close(fd1); 575 close(fd2); 576 577 cvs_worklist_run(&temp_files, cvs_worklist_unlink); 578 579 if (p1 != NULL) 580 xfree(p1); 581 if (p2 != NULL) 582 xfree(p2); 583 584 cleanup: 585 if (diff_rev1 != NULL && 586 (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) && 587 (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev)) 588 xfree(diff_rev1); 589 diff_rev1 = NULL; 590 591 if (diff_rev2 != NULL && 592 (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head)) 593 xfree(diff_rev2); 594 diff_rev2 = NULL; 595 } 596