1 /* $OpenBSD: checkout.c,v 1.167 2010/07/30 21:47:18 ray 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/param.h> 19 #include <sys/dirent.h> 20 #include <sys/stat.h> 21 #include <sys/time.h> 22 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <libgen.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <time.h> 29 #include <unistd.h> 30 31 #include "cvs.h" 32 #include "diff.h" 33 #include "remote.h" 34 35 static void checkout_check_repository(int, char **); 36 static int checkout_classify(const char *, const char *); 37 static void checkout_repository(const char *, const char *); 38 39 extern int print_stdout; 40 extern int prune_dirs; 41 extern int build_dirs; 42 43 static int flags = CR_REPO | CR_RECURSE_DIRS; 44 static int Aflag = 0; 45 static char *dflag = NULL; 46 static char *koptstr = NULL; 47 static char *dateflag = NULL; 48 49 static int nflag = 0; 50 51 static char lastwd[MAXPATHLEN]; 52 char *checkout_target_dir = NULL; 53 54 time_t cvs_specified_date = -1; 55 time_t cvs_directory_date = -1; 56 int disable_fast_checkout = 0; 57 58 struct cvs_cmd cvs_cmd_checkout = { 59 CVS_OP_CHECKOUT, CVS_USE_WDIR, "checkout", 60 { "co", "get" }, 61 "Checkout a working copy of a repository", 62 "[-AcflNnPpRs] [-D date | -r tag] [-d dir] [-j rev] [-k mode] " 63 "[-t id] module ...", 64 "AcD:d:fj:k:lNnPpRr:st:", 65 NULL, 66 cvs_checkout 67 }; 68 69 struct cvs_cmd cvs_cmd_export = { 70 CVS_OP_EXPORT, CVS_USE_WDIR, "export", 71 { "exp", "ex" }, 72 "Export sources from CVS, similar to checkout", 73 "[-flNnR] [-d dir] [-k mode] -D date | -r rev module ...", 74 "D:d:k:flNnRr:", 75 NULL, 76 cvs_export 77 }; 78 79 int 80 cvs_checkout(int argc, char **argv) 81 { 82 int ch; 83 84 while ((ch = getopt(argc, argv, cvs_cmd_checkout.cmd_opts)) != -1) { 85 switch (ch) { 86 case 'A': 87 Aflag = 1; 88 if (koptstr == NULL) 89 reset_option = 1; 90 if (cvs_specified_tag == NULL) 91 reset_tag = 1; 92 break; 93 case 'c': 94 cvs_modules_list(); 95 exit(0); 96 case 'D': 97 dateflag = optarg; 98 if ((cvs_specified_date = date_parse(dateflag)) == -1) 99 fatal("invalid date: %s", dateflag); 100 reset_tag = 0; 101 break; 102 case 'd': 103 if (dflag != NULL) 104 fatal("-d specified two or more times"); 105 dflag = optarg; 106 checkout_target_dir = dflag; 107 108 if (cvs_server_active == 1) 109 disable_fast_checkout = 1; 110 break; 111 case 'j': 112 if (cvs_join_rev1 == NULL) 113 cvs_join_rev1 = optarg; 114 else if (cvs_join_rev2 == NULL) 115 cvs_join_rev2 = optarg; 116 else 117 fatal("too many -j options"); 118 break; 119 case 'k': 120 reset_option = 0; 121 koptstr = optarg; 122 kflag = rcs_kflag_get(koptstr); 123 if (RCS_KWEXP_INVAL(kflag)) { 124 cvs_log(LP_ERR, 125 "invalid RCS keyword expansion mode"); 126 fatal("%s", cvs_cmd_checkout.cmd_synopsis); 127 } 128 break; 129 case 'l': 130 flags &= ~CR_RECURSE_DIRS; 131 break; 132 case 'N': 133 break; 134 case 'n': 135 nflag = 1; 136 break; 137 case 'P': 138 prune_dirs = 1; 139 break; 140 case 'p': 141 cmdp->cmd_flags &= ~CVS_USE_WDIR; 142 print_stdout = 1; 143 cvs_noexec = 1; 144 nflag = 1; 145 break; 146 case 'R': 147 flags |= CR_RECURSE_DIRS; 148 break; 149 case 'r': 150 reset_tag = 0; 151 cvs_specified_tag = optarg; 152 break; 153 default: 154 fatal("%s", cvs_cmd_checkout.cmd_synopsis); 155 } 156 } 157 158 argc -= optind; 159 argv += optind; 160 161 if (argc == 0) 162 fatal("%s", cvs_cmd_checkout.cmd_synopsis); 163 164 if (cvs_server_active == 1 && disable_fast_checkout != 1) { 165 cmdp->cmd_flags &= ~CVS_USE_WDIR; 166 cvs_noexec = 1; 167 } 168 169 checkout_check_repository(argc, argv); 170 171 if (cvs_server_active == 1 && disable_fast_checkout != 1) 172 cvs_noexec = 0; 173 174 return (0); 175 } 176 177 int 178 cvs_export(int argc, char **argv) 179 { 180 int ch; 181 182 prune_dirs = 1; 183 184 while ((ch = getopt(argc, argv, cvs_cmd_export.cmd_opts)) != -1) { 185 switch (ch) { 186 case 'd': 187 if (dflag != NULL) 188 fatal("-d specified two or more times"); 189 dflag = optarg; 190 checkout_target_dir = dflag; 191 192 if (cvs_server_active == 1) 193 disable_fast_checkout = 1; 194 break; 195 case 'k': 196 koptstr = optarg; 197 kflag = rcs_kflag_get(koptstr); 198 if (RCS_KWEXP_INVAL(kflag)) { 199 cvs_log(LP_ERR, 200 "invalid RCS keyword expansion mode"); 201 fatal("%s", cvs_cmd_export.cmd_synopsis); 202 } 203 break; 204 case 'l': 205 flags &= ~CR_RECURSE_DIRS; 206 break; 207 case 'N': 208 break; 209 case 'R': 210 flags |= CR_RECURSE_DIRS; 211 break; 212 case 'r': 213 cvs_specified_tag = optarg; 214 break; 215 default: 216 fatal("%s", cvs_cmd_export.cmd_synopsis); 217 } 218 } 219 220 argc -= optind; 221 argv += optind; 222 223 if (cvs_specified_tag == NULL) 224 fatal("must specify a tag or date"); 225 226 if (argc == 0) 227 fatal("%s", cvs_cmd_export.cmd_synopsis); 228 229 checkout_check_repository(argc, argv); 230 231 return (0); 232 } 233 234 static void 235 checkout_check_repository(int argc, char **argv) 236 { 237 int i; 238 char *wdir, *d; 239 struct cvs_recursion cr; 240 struct module_checkout *mc; 241 struct cvs_ignpat *ip; 242 struct cvs_filelist *fl, *nxt; 243 char repo[MAXPATHLEN], fpath[MAXPATHLEN], *f[1]; 244 245 build_dirs = print_stdout ? 0 : 1; 246 247 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 248 cvs_client_connect_to_server(); 249 250 if (cvs_specified_tag != NULL) 251 cvs_client_send_request("Argument -r%s", 252 cvs_specified_tag); 253 if (Aflag) 254 cvs_client_send_request("Argument -A"); 255 256 if (dateflag != NULL) 257 cvs_client_send_request("Argument -D%s", dateflag); 258 259 if (kflag) 260 cvs_client_send_request("Argument -k%s", koptstr); 261 262 if (dflag != NULL) 263 cvs_client_send_request("Argument -d%s", dflag); 264 265 if (!(flags & CR_RECURSE_DIRS)) 266 cvs_client_send_request("Argument -l"); 267 268 if (cvs_cmdop == CVS_OP_CHECKOUT && prune_dirs == 1) 269 cvs_client_send_request("Argument -P"); 270 271 if (print_stdout == 1) 272 cvs_client_send_request("Argument -p"); 273 274 if (nflag == 1) 275 cvs_client_send_request("Argument -n"); 276 277 cr.enterdir = NULL; 278 cr.leavedir = NULL; 279 if (print_stdout) 280 cr.fileproc = NULL; 281 else 282 cr.fileproc = cvs_client_sendfile; 283 284 flags &= ~CR_REPO; 285 cr.flags = flags; 286 287 if (cvs_cmdop != CVS_OP_EXPORT) 288 cvs_file_run(argc, argv, &cr); 289 290 cvs_client_send_files(argv, argc); 291 cvs_client_senddir("."); 292 293 cvs_client_send_request("%s", 294 (cvs_cmdop == CVS_OP_CHECKOUT) ? "co" : "export"); 295 296 cvs_client_get_responses(); 297 298 return; 299 } 300 301 for (i = 0; i < argc; i++) { 302 mc = cvs_module_lookup(argv[i]); 303 current_module = mc; 304 305 RB_FOREACH(fl, cvs_flisthead, &(mc->mc_ignores)) 306 cvs_file_ignore(fl->file_path, &checkout_ign_pats); 307 308 RB_FOREACH(fl, cvs_flisthead, &(mc->mc_modules)) { 309 module_repo_root = NULL; 310 311 (void)xsnprintf(repo, sizeof(repo), "%s/%s", 312 current_cvsroot->cr_dir, fl->file_path); 313 314 if (!(mc->mc_flags & MODULE_ALIAS) || dflag != NULL) 315 module_repo_root = xstrdup(fl->file_path); 316 317 if (mc->mc_flags & MODULE_NORECURSE) 318 flags &= ~CR_RECURSE_DIRS; 319 320 if (dflag != NULL) 321 wdir = dflag; 322 else if (mc->mc_flags & MODULE_ALIAS) 323 wdir = fl->file_path; 324 else 325 wdir = mc->mc_name; 326 327 switch (checkout_classify(repo, fl->file_path)) { 328 case CVS_FILE: 329 cr.fileproc = cvs_update_local; 330 cr.flags = flags; 331 332 if (!(mc->mc_flags & MODULE_ALIAS)) { 333 module_repo_root = 334 xstrdup(dirname(fl->file_path)); 335 d = wdir; 336 (void)xsnprintf(fpath, sizeof(fpath), 337 "%s/%s", d, 338 basename(fl->file_path)); 339 } else { 340 d = dirname(wdir); 341 strlcpy(fpath, fl->file_path, 342 sizeof(fpath)); 343 } 344 345 if (build_dirs == 1) 346 cvs_mkpath(d, cvs_specified_tag); 347 348 f[0] = fpath; 349 cvs_file_run(1, f, &cr); 350 break; 351 case CVS_DIR: 352 if (build_dirs == 1) 353 cvs_mkpath(wdir, cvs_specified_tag); 354 checkout_repository(repo, wdir); 355 break; 356 default: 357 break; 358 } 359 360 if (nflag != 1 && mc->mc_prog != NULL && 361 mc->mc_flags & MODULE_RUN_ON_CHECKOUT) 362 cvs_exec(mc->mc_prog, NULL, 0); 363 364 if (module_repo_root != NULL) 365 xfree(module_repo_root); 366 } 367 368 if (mc->mc_canfree == 1) { 369 for (fl = RB_MIN(cvs_flisthead, &(mc->mc_modules)); 370 fl != NULL; fl = nxt) { 371 nxt = RB_NEXT(cvs_flisthead, 372 &(mc->mc_modules), fl); 373 RB_REMOVE(cvs_flisthead, 374 &(mc->mc_modules), fl); 375 xfree(fl->file_path); 376 xfree(fl); 377 } 378 } 379 380 while ((ip = TAILQ_FIRST(&checkout_ign_pats)) != NULL) { 381 TAILQ_REMOVE(&checkout_ign_pats, ip, ip_list); 382 xfree(ip); 383 } 384 385 xfree(mc); 386 } 387 } 388 389 static int 390 checkout_classify(const char *repo, const char *arg) 391 { 392 char *d, *f, fpath[MAXPATHLEN]; 393 struct stat sb; 394 395 if (stat(repo, &sb) == 0) { 396 if (S_ISDIR(sb.st_mode)) 397 return CVS_DIR; 398 } 399 400 d = dirname(repo); 401 f = basename(repo); 402 403 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s%s", d, f, RCS_FILE_EXT); 404 if (stat(fpath, &sb) == 0) { 405 if (!S_ISREG(sb.st_mode)) { 406 cvs_log(LP_ERR, "ignoring %s: not a regular file", arg); 407 return 0; 408 } 409 return CVS_FILE; 410 } 411 412 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s/%s%s", 413 d, CVS_PATH_ATTIC, f, RCS_FILE_EXT); 414 if (stat(fpath, &sb) == 0) { 415 if (!S_ISREG(sb.st_mode)) { 416 cvs_log(LP_ERR, "ignoring %s: not a regular file", arg); 417 return 0; 418 } 419 return CVS_FILE; 420 } 421 422 cvs_log(LP_ERR, "cannot find module `%s' - ignored", arg); 423 return 0; 424 } 425 426 static void 427 checkout_repository(const char *repobase, const char *wdbase) 428 { 429 struct cvs_flisthead fl, dl; 430 struct cvs_recursion cr; 431 432 RB_INIT(&fl); 433 RB_INIT(&dl); 434 435 cvs_history_add((cvs_cmdop == CVS_OP_CHECKOUT) ? 436 CVS_HISTORY_CHECKOUT : CVS_HISTORY_EXPORT, NULL, wdbase); 437 438 if (print_stdout) { 439 cr.enterdir = NULL; 440 cr.leavedir = NULL; 441 } else { 442 cr.enterdir = cvs_update_enterdir; 443 if (cvs_server_active == 1) { 444 if (disable_fast_checkout != 1) 445 cr.leavedir = NULL; 446 else 447 cr.leavedir = cvs_update_leavedir; 448 } else { 449 cr.leavedir = prune_dirs ? cvs_update_leavedir : NULL; 450 } 451 } 452 cr.fileproc = cvs_update_local; 453 cr.flags = flags; 454 455 cvs_repository_lock(repobase, 0); 456 cvs_repository_getdir(repobase, wdbase, &fl, &dl, 457 flags & CR_RECURSE_DIRS ? REPOSITORY_DODIRS : 0); 458 459 cvs_file_walklist(&fl, &cr); 460 cvs_file_freelist(&fl); 461 462 cvs_repository_unlock(repobase); 463 464 cvs_file_walklist(&dl, &cr); 465 cvs_file_freelist(&dl); 466 } 467 468 void 469 cvs_checkout_file(struct cvs_file *cf, RCSNUM *rnum, char *tag, int co_flags) 470 { 471 BUF *bp; 472 mode_t mode; 473 int cf_kflag, exists; 474 time_t rcstime; 475 CVSENTRIES *ent; 476 struct timeval tv[2]; 477 struct tm datetm; 478 char *entry, *tosend; 479 char kbuf[8], sticky[CVS_REV_BUFSZ], rev[CVS_REV_BUFSZ]; 480 char timebuf[CVS_TIME_BUFSZ], tbuf[CVS_TIME_BUFSZ]; 481 482 exists = 0; 483 tosend = NULL; 484 485 if (!(co_flags & CO_REMOVE)) 486 rcsnum_tostr(rnum, rev, sizeof(rev)); 487 else 488 rev[0] = '\0'; 489 490 cvs_log(LP_TRACE, "cvs_checkout_file(%s, %s, %d) -> %s", 491 cf->file_path, rev, co_flags, 492 (cvs_server_active) ? "to client" : "to disk"); 493 494 if (co_flags & CO_DUMP) { 495 rcs_rev_write_fd(cf->file_rcs, rnum, STDOUT_FILENO, 0); 496 return; 497 } 498 499 if (cvs_server_active == 0) { 500 (void)unlink(cf->file_path); 501 502 if (!(co_flags & CO_MERGE)) { 503 if (cf->file_flags & FILE_ON_DISK) { 504 exists = 1; 505 (void)close(cf->fd); 506 } 507 508 cf->fd = open(cf->file_path, 509 O_CREAT | O_RDWR | O_TRUNC); 510 if (cf->fd == -1) 511 fatal("cvs_checkout_file: open: %s", 512 strerror(errno)); 513 514 rcs_rev_write_fd(cf->file_rcs, rnum, cf->fd, 0); 515 cf->file_flags |= FILE_ON_DISK; 516 } else { 517 cvs_merge_file(cf, (cvs_join_rev1 == NULL)); 518 } 519 520 mode = cf->file_rcs->rf_mode; 521 mode |= S_IWUSR; 522 523 if (fchmod(cf->fd, mode) == -1) 524 fatal("cvs_checkout_file: fchmod: %s", strerror(errno)); 525 526 if ((exists == 0) && (cf->file_ent == NULL) && 527 !(co_flags & CO_MERGE)) 528 rcstime = rcs_rev_getdate(cf->file_rcs, rnum); 529 else 530 time(&rcstime); 531 532 tv[0].tv_sec = rcstime; 533 tv[0].tv_usec = 0; 534 tv[1] = tv[0]; 535 if (futimes(cf->fd, tv) == -1) 536 fatal("cvs_checkout_file: futimes: %s", 537 strerror(errno)); 538 } else { 539 time(&rcstime); 540 } 541 542 gmtime_r(&rcstime, &datetm); 543 asctime_r(&datetm, tbuf); 544 tbuf[strcspn(tbuf, "\n")] = '\0'; 545 546 if (co_flags & CO_MERGE) { 547 (void)xsnprintf(timebuf, sizeof(timebuf), "Result of merge+%s", 548 tbuf); 549 } else { 550 strlcpy(timebuf, tbuf, sizeof(timebuf)); 551 } 552 553 if (reset_tag) { 554 sticky[0] = '\0'; 555 } else if (co_flags & CO_SETSTICKY) 556 if (tag != NULL) 557 (void)xsnprintf(sticky, sizeof(sticky), "T%s", tag); 558 else if (cvs_specified_date != -1) { 559 gmtime_r(&cvs_specified_date, &datetm); 560 (void)strftime(sticky, sizeof(sticky), 561 "D"CVS_DATE_FMT, &datetm); 562 } else if (cvs_directory_date != -1) { 563 gmtime_r(&cvs_directory_date, &datetm); 564 (void)strftime(sticky, sizeof(sticky), 565 "D"CVS_DATE_FMT, &datetm); 566 } else 567 (void)xsnprintf(sticky, sizeof(sticky), "T%s", rev); 568 else if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL) 569 (void)xsnprintf(sticky, sizeof(sticky), "T%s", 570 cf->file_ent->ce_tag); 571 else 572 sticky[0] = '\0'; 573 574 kbuf[0] = '\0'; 575 if (cf->file_rcs != NULL && cf->file_rcs->rf_expand != NULL) { 576 cf_kflag = rcs_kflag_get(cf->file_rcs->rf_expand); 577 if (kflag || cf_kflag != RCS_KWEXP_DEFAULT) 578 (void)xsnprintf(kbuf, sizeof(kbuf), 579 "-k%s", cf->file_rcs->rf_expand); 580 } else if (!reset_option && cf->file_ent != NULL) { 581 if (cf->file_ent->ce_opts != NULL) 582 strlcpy(kbuf, cf->file_ent->ce_opts, sizeof(kbuf)); 583 } 584 585 entry = xmalloc(CVS_ENT_MAXLINELEN); 586 cvs_ent_line_str(cf->file_name, rev, timebuf, kbuf, sticky, 0, 0, 587 entry, CVS_ENT_MAXLINELEN); 588 589 if (cvs_server_active == 0) { 590 if (!(co_flags & CO_REMOVE) && cvs_cmdop != CVS_OP_EXPORT) { 591 ent = cvs_ent_open(cf->file_wd); 592 cvs_ent_add(ent, entry); 593 cf->file_ent = cvs_ent_parse(entry); 594 } 595 } else { 596 if (co_flags & CO_MERGE) { 597 (void)unlink(cf->file_path); 598 cvs_merge_file(cf, (cvs_join_rev1 == NULL)); 599 tosend = cf->file_path; 600 } 601 602 /* 603 * If this file has a tag, push out the Directory with the 604 * tag to the client. Except when this file was explicitly 605 * specified on the command line. 606 */ 607 if (tag != NULL && strcmp(cf->file_wd, lastwd) && 608 !(cf->file_flags & FILE_USER_SUPPLIED)) { 609 strlcpy(lastwd, cf->file_wd, MAXPATHLEN); 610 cvs_server_set_sticky(cf->file_wd, sticky); 611 } 612 613 if (co_flags & CO_COMMIT) 614 cvs_server_update_entry("Updated", cf); 615 else if (co_flags & CO_MERGE) 616 cvs_server_update_entry("Merged", cf); 617 else if (co_flags & CO_REMOVE) 618 cvs_server_update_entry("Removed", cf); 619 else 620 cvs_server_update_entry("Updated", cf); 621 622 if (!(co_flags & CO_REMOVE)) { 623 cvs_remote_output(entry); 624 625 if (!(co_flags & CO_MERGE)) { 626 mode = cf->file_rcs->rf_mode; 627 mode |= S_IWUSR; 628 bp = rcs_rev_getbuf(cf->file_rcs, rnum, 0); 629 cvs_remote_send_file_buf(cf->file_path, 630 bp, mode); 631 } else { 632 cvs_remote_send_file(tosend, cf->fd); 633 } 634 } 635 } 636 637 xfree(entry); 638 } 639