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