1 /* $OpenBSD: diff.c,v 1.134 2008/03/13 19:54:34 sthen Exp $ */ 2 /* 3 * Copyright (c) 2006 Joris Vink <joris@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/stat.h> 19 #include <sys/time.h> 20 21 #include <errno.h> 22 #include <fcntl.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include "cvs.h" 28 #include "diff.h" 29 #include "remote.h" 30 31 void cvs_diff_local(struct cvs_file *); 32 33 static int Nflag = 0; 34 static int force_head = 0; 35 static char *koptstr; 36 static char *rev1 = NULL; 37 static char *rev2 = NULL; 38 39 struct cvs_cmd cvs_cmd_diff = { 40 CVS_OP_DIFF, CVS_USE_WDIR, "diff", 41 { "di", "dif" }, 42 "Show differences between revisions", 43 "[-cilNnpRu] [[-D date] [-r rev] [-D date2 | -r rev2]] " 44 "[-k mode] [file ...]", 45 "cfD:ik:lNnpr:Ru", 46 NULL, 47 cvs_diff 48 }; 49 50 struct cvs_cmd cvs_cmd_rdiff = { 51 CVS_OP_RDIFF, 0, "rdiff", 52 { "patch", "pa" }, 53 "Show differences between revisions", 54 "[-flR] [-c | -u] [-s | -t] [-V ver] -D date | -r rev\n" 55 "[-D date2 | -r rev2] [-k mode] module ...", 56 "cfD:k:lr:RuV:", 57 NULL, 58 cvs_diff 59 }; 60 61 int 62 cvs_diff(int argc, char **argv) 63 { 64 int ch, flags; 65 char *arg = "."; 66 struct cvs_recursion cr; 67 68 flags = CR_RECURSE_DIRS; 69 strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff", 70 sizeof(diffargs)); 71 72 while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ? 73 cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) { 74 switch (ch) { 75 case 'c': 76 strlcat(diffargs, " -c", sizeof(diffargs)); 77 diff_format = D_CONTEXT; 78 break; 79 case 'f': 80 force_head = 1; 81 break; 82 case 'i': 83 strlcat(diffargs, " -i", sizeof(diffargs)); 84 diff_iflag = 1; 85 break; 86 case 'k': 87 koptstr = optarg; 88 kflag = rcs_kflag_get(koptstr); 89 if (RCS_KWEXP_INVAL(kflag)) { 90 cvs_log(LP_ERR, 91 "invalid RCS keyword expension mode"); 92 fatal("%s", cvs_cmdop == CVS_OP_DIFF ? 93 cvs_cmd_diff.cmd_synopsis : 94 cvs_cmd_rdiff.cmd_synopsis); 95 } 96 break; 97 case 'l': 98 flags &= ~CR_RECURSE_DIRS; 99 break; 100 case 'n': 101 strlcat(diffargs, " -n", sizeof(diffargs)); 102 diff_format = D_RCSDIFF; 103 break; 104 case 'N': 105 Nflag = 1; 106 break; 107 case 'p': 108 strlcat(diffargs, " -p", sizeof(diffargs)); 109 diff_pflag = 1; 110 break; 111 case 'R': 112 flags |= CR_RECURSE_DIRS; 113 break; 114 case 'r': 115 if (rev1 == NULL) { 116 rev1 = optarg; 117 } else if (rev2 == NULL) { 118 rev2 = optarg; 119 } else { 120 fatal("no more than 2 revisions/dates can" 121 " be specified"); 122 } 123 break; 124 case 'u': 125 strlcat(diffargs, " -u", sizeof(diffargs)); 126 diff_format = D_UNIFIED; 127 break; 128 case 'V': 129 fatal("the -V option is obsolete " 130 "and should not be used"); 131 default: 132 fatal("%s", cvs_cmdop == CVS_OP_DIFF ? 133 cvs_cmd_diff.cmd_synopsis : 134 cvs_cmd_rdiff.cmd_synopsis); 135 } 136 } 137 138 argc -= optind; 139 argv += optind; 140 141 cr.enterdir = NULL; 142 cr.leavedir = NULL; 143 144 if (cvs_cmdop == CVS_OP_RDIFF) { 145 if (rev1 == NULL) 146 fatal("must specify at least one revision/date!"); 147 148 if (!diff_format) { 149 strlcat(diffargs, " -c", sizeof(diffargs)); 150 diff_format = D_CONTEXT; 151 } 152 153 flags |= CR_REPO; 154 } 155 156 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 157 cvs_client_connect_to_server(); 158 cr.fileproc = cvs_client_sendfile; 159 160 if (!(flags & CR_RECURSE_DIRS)) 161 cvs_client_send_request("Argument -l"); 162 163 if (kflag) 164 cvs_client_send_request("Argument -k%s", koptstr); 165 166 switch (diff_format) { 167 case D_CONTEXT: 168 cvs_client_send_request("Argument -c"); 169 break; 170 case D_RCSDIFF: 171 cvs_client_send_request("Argument -n"); 172 break; 173 case D_UNIFIED: 174 cvs_client_send_request("Argument -u"); 175 break; 176 default: 177 break; 178 } 179 180 if (Nflag == 1) 181 cvs_client_send_request("Argument -N"); 182 183 if (diff_pflag == 1) 184 cvs_client_send_request("Argument -p"); 185 186 if (rev1 != NULL) 187 cvs_client_send_request("Argument -r%s", rev1); 188 if (rev2 != NULL) 189 cvs_client_send_request("Argument -r%s", rev2); 190 } else { 191 if (cvs_cmdop == CVS_OP_RDIFF && 192 chdir(current_cvsroot->cr_dir) == -1) 193 fatal("cvs_diff: %s", strerror(errno)); 194 195 cr.fileproc = cvs_diff_local; 196 } 197 198 cr.flags = flags; 199 200 diff_rev1 = diff_rev2 = NULL; 201 202 if (cvs_cmdop == CVS_OP_DIFF || 203 current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 204 if (argc > 0) 205 cvs_file_run(argc, argv, &cr); 206 else 207 cvs_file_run(1, &arg, &cr); 208 } 209 210 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 211 cvs_client_send_files(argv, argc); 212 cvs_client_senddir("."); 213 214 cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ? 215 "rdiff" : "diff"); 216 217 cvs_client_get_responses(); 218 } 219 220 return (0); 221 } 222 223 void 224 cvs_diff_local(struct cvs_file *cf) 225 { 226 RCSNUM *r1; 227 BUF *b1; 228 int fd1, fd2; 229 struct stat st; 230 struct timeval tv[2], tv2[2]; 231 char rbuf[CVS_REV_BUFSZ], *p1, *p2; 232 233 r1 = NULL; 234 b1 = NULL; 235 236 cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path); 237 238 if (cf->file_type == CVS_DIR) { 239 if (verbosity > 1) 240 cvs_log(LP_NOTICE, "Diffing inside %s", cf->file_path); 241 return; 242 } 243 244 cvs_file_classify(cf, cvs_directory_tag); 245 246 if (cf->file_status == FILE_LOST) { 247 cvs_log(LP_ERR, "cannot find file %s", cf->file_path); 248 return; 249 } else if (cf->file_status == FILE_UNKNOWN) { 250 cvs_log(LP_ERR, "I know nothing about %s", cf->file_path); 251 return; 252 } else if (cf->file_status == FILE_ADDED && Nflag == 0) { 253 cvs_log(LP_ERR, "%s is a new entry, no comparison available", 254 cf->file_path); 255 return; 256 } else if (cf->file_status == FILE_REMOVED && Nflag == 0) { 257 cvs_log(LP_ERR, "%s was removed, no comparison available", 258 cf->file_path); 259 return; 260 } else if (cf->file_status == FILE_UPTODATE && rev1 == NULL && 261 rev2 == NULL) { 262 return; 263 } 264 265 if (kflag) 266 rcs_kwexp_set(cf->file_rcs, kflag); 267 268 if (rev1 != NULL) 269 if ((diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs)) == 270 NULL) { 271 if (cvs_cmdop == CVS_OP_DIFF) { 272 cvs_log(LP_ERR, "tag %s is not in file %s", 273 rev1, cf->file_path); 274 return; 275 } 276 if (force_head) { 277 /* -f is not allowed for unknown symbols */ 278 diff_rev1 = rcsnum_parse(rev1); 279 if (diff_rev1 == NULL) 280 fatal("no such tag %s", rev1); 281 rcsnum_free(diff_rev1); 282 283 diff_rev1 = rcsnum_alloc(); 284 rcsnum_cpy(cf->file_rcs->rf_head, diff_rev1, 0); 285 } 286 } 287 288 if (rev2 != NULL) 289 if ((diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs)) == 290 NULL) { 291 if (cvs_cmdop == CVS_OP_DIFF) { 292 rcsnum_free(diff_rev1); 293 cvs_log(LP_ERR, "tag %s is not in file %s", 294 rev2, cf->file_path); 295 return; 296 } 297 if (force_head) { 298 /* -f is not allowed for unknown symbols */ 299 diff_rev2 = rcsnum_parse(rev2); 300 if (diff_rev2 == NULL) 301 fatal("no such tag %s", rev2); 302 rcsnum_free(diff_rev2); 303 304 diff_rev2 = rcsnum_alloc(); 305 rcsnum_cpy(cf->file_rcs->rf_head, diff_rev2, 0); 306 } 307 } 308 309 if (cvs_cmdop == CVS_OP_RDIFF && diff_rev1 == NULL && diff_rev2 == NULL) 310 return; 311 312 diff_file = cf->file_path; 313 314 cvs_printf("Index: %s\n", cf->file_path); 315 if (cvs_cmdop == CVS_OP_DIFF) 316 cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV, cf->file_rpath); 317 318 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 319 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 320 321 if (cf->file_status != FILE_ADDED) { 322 if (diff_rev1 != NULL) 323 r1 = diff_rev1; 324 else if (cf->file_ent != NULL) 325 r1 = cf->file_ent->ce_rev; 326 else 327 r1 = NULL; 328 329 diff_rev1 = r1; 330 331 if (diff_rev1 != NULL) { 332 (void)rcsnum_tostr(r1, rbuf, sizeof(rbuf)); 333 334 tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, r1); 335 tv[0].tv_usec = 0; 336 tv[1] = tv[0]; 337 338 if (cvs_cmdop == CVS_OP_DIFF) 339 cvs_printf("retrieving revision %s\n", rbuf); 340 fd1 = rcs_rev_write_stmp(cf->file_rcs, r1, p1, 0); 341 if (futimes(fd1, tv) == -1) 342 fatal("cvs_diff_local: utimes failed"); 343 } 344 } 345 346 if (diff_rev2 != NULL && cf->file_status != FILE_ADDED && 347 cf->file_status != FILE_REMOVED) { 348 (void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf)); 349 350 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2); 351 tv2[0].tv_usec = 0; 352 tv2[1] = tv2[0]; 353 354 if (cvs_cmdop == CVS_OP_DIFF) 355 cvs_printf("retrieving revision %s\n", rbuf); 356 fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0); 357 if (futimes(fd2, tv2) == -1) 358 fatal("cvs_diff_local: utimes failed"); 359 } else if (cf->file_status != FILE_REMOVED) { 360 if (cvs_cmdop == CVS_OP_RDIFF || (cvs_server_active == 1 && 361 cf->file_status != FILE_MODIFIED)) { 362 if (diff_rev2 != NULL) { 363 tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, 364 cf->file_rcsrev); 365 tv2[0].tv_usec = 0; 366 tv2[1] = tv2[0]; 367 368 fd2 = rcs_rev_write_stmp(cf->file_rcs, 369 cf->file_rcsrev, p2, 0); 370 if (futimes(fd2, tv2) == -1) 371 fatal("cvs_diff_local: utimes failed"); 372 } 373 } else { 374 if (fstat(cf->fd, &st) == -1) 375 fatal("fstat failed %s", strerror(errno)); 376 b1 = cvs_buf_load_fd(cf->fd); 377 378 tv2[0].tv_sec = st.st_mtime; 379 tv2[0].tv_usec = 0; 380 tv2[1] = tv2[0]; 381 382 fd2 = cvs_buf_write_stmp(b1, p2, tv2); 383 cvs_buf_free(b1); 384 } 385 } 386 387 if (cvs_cmdop == CVS_OP_DIFF) { 388 cvs_printf("%s", diffargs); 389 390 if (cf->file_status != FILE_ADDED) { 391 (void)rcsnum_tostr(r1, rbuf, sizeof(rbuf)); 392 cvs_printf(" -r%s", rbuf); 393 394 if (diff_rev2 != NULL) { 395 (void)rcsnum_tostr(diff_rev2, rbuf, 396 sizeof(rbuf)); 397 cvs_printf(" -r%s", rbuf); 398 } 399 } 400 401 if (diff_rev2 == NULL) 402 cvs_printf(" %s", cf->file_name); 403 cvs_printf("\n"); 404 } else { 405 cvs_printf("diff "); 406 switch (diff_format) { 407 case D_CONTEXT: 408 cvs_printf("-c "); 409 break; 410 case D_RCSDIFF: 411 cvs_printf("-n "); 412 break; 413 case D_UNIFIED: 414 cvs_printf("-u "); 415 break; 416 default: 417 break; 418 } 419 if (diff_rev1 == NULL) { 420 cvs_printf("%s ", CVS_PATH_DEVNULL); 421 } else { 422 (void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf)); 423 cvs_printf("%s:%s ", cf->file_path, rbuf); 424 } 425 426 if (diff_rev2 == NULL) { 427 cvs_printf("%s:removed\n", cf->file_path); 428 } else { 429 (void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 : 430 cf->file_rcs->rf_head, rbuf, sizeof(rbuf)); 431 cvs_printf("%s:%s\n", cf->file_path, rbuf); 432 } 433 } 434 435 if (cf->file_status == FILE_ADDED || 436 (cvs_cmdop == CVS_OP_RDIFF && diff_rev1 == NULL)) { 437 xfree(p1); 438 close(fd1); 439 (void)xasprintf(&p1, "%s", CVS_PATH_DEVNULL); 440 if ((fd1 = open(p1, O_RDONLY)) == -1) 441 fatal("cvs_diff_local: cannot open %s", 442 CVS_PATH_DEVNULL); 443 } else if (cf->file_status == FILE_REMOVED || 444 (cvs_cmdop == CVS_OP_RDIFF && diff_rev2 == NULL)) { 445 xfree(p2); 446 close(fd2); 447 (void)xasprintf(&p2, "%s", CVS_PATH_DEVNULL); 448 if ((fd2 = open(p2, O_RDONLY)) == -1) 449 fatal("cvs_diff_local: cannot open %s", 450 CVS_PATH_DEVNULL); 451 } 452 453 if (cvs_diffreg(p1, p2, fd1, fd2, NULL) == D_ERROR) 454 fatal("cvs_diff_local: failed to get RCS patch"); 455 456 close(fd1); 457 close(fd2); 458 459 cvs_worklist_run(&temp_files, cvs_worklist_unlink); 460 461 if (p1 != NULL) 462 xfree(p1); 463 if (p2 != NULL) 464 xfree(p2); 465 466 if (diff_rev1 != NULL && diff_rev1 != cf->file_rcs->rf_head && 467 (cf->file_ent != NULL && diff_rev1 != cf->file_ent->ce_rev)) 468 rcsnum_free(diff_rev1); 469 if (diff_rev2 != NULL) 470 rcsnum_free(diff_rev2); 471 472 diff_rev1 = diff_rev2 = NULL; 473 } 474