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