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