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