1 /* $OpenBSD: diff.c,v 1.144 2008/06/20 14:04:29 tobias 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_NOTICE, "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->fd == -1) { 290 if (!cvs_server_active) 291 cvs_log(LP_ERR, "cannot find %s", 292 cf->file_path); 293 return; 294 } 295 break; 296 case CVS_ENT_REMOVED: 297 if (Nflag == 0) { 298 cvs_log(LP_ERR, "%s was removed, no " 299 "comparison available", cf->file_path); 300 return; 301 } 302 if (cf->file_rcs == NULL) { 303 cvs_log(LP_ERR, "cannot find RCS file for %s", 304 cf->file_path); 305 return; 306 } 307 break; 308 default: 309 if (cvs_server_active != 1 && cf->fd == -1) { 310 cvs_log(LP_ERR, "cannot find %s", 311 cf->file_path); 312 return; 313 } 314 315 if (cf->file_rcs == NULL) { 316 cvs_log(LP_ERR, "cannot find RCS file for %s", 317 cf->file_path); 318 return; 319 } 320 break; 321 } 322 } 323 324 if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL && 325 date1 == -1 && date2 == -1) 326 return; 327 328 if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) { 329 cvs_log(LP_ERR, "no head revision in RCS file for %s\n", 330 cf->file_path); 331 return; 332 } 333 334 if (kflag && cf->file_rcs != NULL) 335 rcs_kwexp_set(cf->file_rcs, kflag); 336 337 if (cf->file_rcs == NULL) 338 diff_rev1 = NULL; 339 else if (rev1 != NULL || date1 != -1) { 340 cvs_specified_date = date1; 341 diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs); 342 if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) { 343 if (rev1 != NULL) { 344 cvs_log(LP_ERR, "tag %s not in file %s", rev1, 345 cf->file_path); 346 goto cleanup; 347 } else if (Nflag) { 348 diff_rev1 = NULL; 349 } else { 350 gmtime_r(&cvs_specified_date, &datetm); 351 strftime(tbuf, sizeof(tbuf), 352 "%Y.%m.%d.%H.%M.%S", &datetm); 353 cvs_log(LP_ERR, "no revision for date %s in " 354 "file %s", tbuf, cf->file_path); 355 goto cleanup; 356 } 357 } else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF && 358 force_head) { 359 /* -f is not allowed for unknown symbols */ 360 if ((diff_rev1 = rcsnum_parse(rev1)) == NULL) 361 fatal("no such tag %s", rev1); 362 rcsnum_free(diff_rev1); 363 364 diff_rev1 = cf->file_rcs->rf_head; 365 } 366 cvs_specified_date = -1; 367 } else if (cvs_cmdop == CVS_OP_DIFF) { 368 if (cf->file_ent->ce_status == CVS_ENT_ADDED) 369 diff_rev1 = NULL; 370 else 371 diff_rev1 = cf->file_ent->ce_rev; 372 } 373 374 if (cf->file_rcs == NULL) 375 diff_rev2 = NULL; 376 else if (rev2 != NULL || date2 != -1) { 377 cvs_specified_date = date2; 378 diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs); 379 if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) { 380 if (rev2 != NULL) { 381 cvs_log(LP_ERR, "tag %s not in file %s", rev2, 382 cf->file_path); 383 goto cleanup; 384 } else if (Nflag) { 385 diff_rev2 = NULL; 386 } else { 387 gmtime_r(&cvs_specified_date, &datetm); 388 strftime(tbuf, sizeof(tbuf), 389 "%Y.%m.%d.%H.%M.%S", &datetm); 390 cvs_log(LP_ERR, "no revision for date %s in " 391 "file %s", tbuf, cf->file_path); 392 goto cleanup; 393 } 394 } else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF && 395 force_head) { 396 /* -f is not allowed for unknown symbols */ 397 if ((diff_rev2 = rcsnum_parse(rev2)) == NULL) 398 fatal("no such tag %s", rev2); 399 rcsnum_free(diff_rev2); 400 401 diff_rev2 = cf->file_rcs->rf_head; 402 } 403 cvs_specified_date = -1; 404 } else if (cvs_cmdop == CVS_OP_RDIFF) 405 diff_rev2 = cf->file_rcs->rf_head; 406 else if (cf->file_ent->ce_status == CVS_ENT_REMOVED) 407 diff_rev2 = NULL; 408 409 if (diff_rev1 != NULL && diff_rev2 != NULL && 410 rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0) 411 goto cleanup; 412 413 switch (cvs_cmdop) { 414 case CVS_OP_DIFF: 415 if (cf->file_status == FILE_UPTODATE && diff_rev1 != NULL && 416 rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0) == 0) 417 goto cleanup; 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 && cf->fd != -1 && 461 cf->file_ent->ce_status != CVS_ENT_REMOVED) { 462 if (fstat(cf->fd, &st) == -1) 463 fatal("fstat failed %s", strerror(errno)); 464 b1 = cvs_buf_load_fd(cf->fd); 465 466 tv2[0].tv_sec = st.st_mtime; 467 tv2[0].tv_usec = 0; 468 tv2[1] = tv2[0]; 469 470 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 471 fd2 = cvs_buf_write_stmp(b1, p2, tv2); 472 cvs_buf_free(b1); 473 } 474 475 switch (cvs_cmdop) { 476 case CVS_OP_DIFF: 477 cvs_printf("%s", diffargs); 478 479 if (rev1 != NULL && diff_rev1 != NULL) { 480 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 481 cvs_printf(" -r%s", rbuf); 482 483 if (rev2 != NULL && diff_rev2 != NULL) { 484 (void)rcsnum_tostr(diff_rev2, rbuf, 485 sizeof(rbuf)); 486 cvs_printf(" -r%s", rbuf); 487 } 488 } 489 490 if (diff_rev2 == NULL) 491 cvs_printf(" %s", cf->file_path); 492 cvs_printf("\n"); 493 break; 494 case CVS_OP_RDIFF: 495 cvs_printf("diff "); 496 switch (diff_format) { 497 case D_CONTEXT: 498 cvs_printf("-c "); 499 break; 500 case D_RCSDIFF: 501 cvs_printf("-n "); 502 break; 503 case D_UNIFIED: 504 cvs_printf("-u "); 505 break; 506 default: 507 break; 508 } 509 if (diff_rev1 == NULL) { 510 cvs_printf("%s ", CVS_PATH_DEVNULL); 511 } else { 512 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 513 cvs_printf("%s:%s ", cf->file_path, rbuf); 514 } 515 516 if (diff_rev2 == NULL) { 517 cvs_printf("%s:removed\n", cf->file_path); 518 } else { 519 (void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 : 520 cf->file_rcs->rf_head, rbuf, sizeof(rbuf)); 521 cvs_printf("%s:%s\n", cf->file_path, rbuf); 522 } 523 break; 524 } 525 526 if (fd1 == -1) { 527 if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1) 528 fatal("cannot open %s", CVS_PATH_DEVNULL); 529 } 530 if (fd2 == -1) { 531 if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1) 532 fatal("cannot open %s", CVS_PATH_DEVNULL); 533 } 534 535 if (cvs_diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL, 536 p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL) 537 == D_ERROR) 538 fatal("cvs_diff_local: failed to get RCS patch"); 539 540 close(fd1); 541 close(fd2); 542 543 cvs_worklist_run(&temp_files, cvs_worklist_unlink); 544 545 if (p1 != NULL) 546 xfree(p1); 547 if (p2 != NULL) 548 xfree(p2); 549 550 cleanup: 551 if (diff_rev1 != NULL && 552 (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) && 553 (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev)) 554 xfree(diff_rev1); 555 diff_rev1 = NULL; 556 557 if (diff_rev2 != NULL && 558 (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head)) 559 xfree(diff_rev2); 560 diff_rev2 = NULL; 561 } 562