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