1 /* $OpenBSD: commit.c,v 1.132 2008/03/09 03:14:52 joris Exp $ */ 2 /* 3 * Copyright (c) 2006 Joris Vink <joris@openbsd.org> 4 * Copyright (c) 2006 Xavier Santolaria <xsa@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/stat.h> 20 21 #include <errno.h> 22 #include <fcntl.h> 23 #include <string.h> 24 #include <unistd.h> 25 26 #include "cvs.h" 27 #include "diff.h" 28 #include "remote.h" 29 30 void cvs_commit_local(struct cvs_file *); 31 void cvs_commit_check_files(struct cvs_file *); 32 void cvs_commit_lock_dirs(struct cvs_file *); 33 34 static BUF *commit_diff(struct cvs_file *, RCSNUM *, int); 35 static void commit_desc_set(struct cvs_file *); 36 37 struct cvs_flisthead files_affected; 38 struct cvs_flisthead files_added; 39 struct cvs_flisthead files_removed; 40 struct cvs_flisthead files_modified; 41 42 int conflicts_found; 43 char *logmsg = NULL; 44 45 struct cvs_cmd cvs_cmd_commit = { 46 CVS_OP_COMMIT, CVS_USE_WDIR | CVS_LOCK_REPO, "commit", 47 { "ci", "com" }, 48 "Check files into the repository", 49 "[-flR] [-F logfile | -m msg] [-r rev] ...", 50 "F:flm:Rr:", 51 NULL, 52 cvs_commit 53 }; 54 55 int 56 cvs_commit(int argc, char **argv) 57 { 58 int flags; 59 int ch, Fflag, mflag; 60 struct module_checkout *mc; 61 struct cvs_recursion cr; 62 char *arg = ".", repo[MAXPATHLEN]; 63 64 flags = CR_RECURSE_DIRS; 65 Fflag = mflag = 0; 66 67 while ((ch = getopt(argc, argv, cvs_cmd_commit.cmd_opts)) != -1) { 68 switch (ch) { 69 case 'F': 70 /* free previously assigned value */ 71 if (logmsg != NULL) 72 xfree(logmsg); 73 logmsg = cvs_logmsg_read(optarg); 74 Fflag = 1; 75 break; 76 case 'f': 77 break; 78 case 'l': 79 flags &= ~CR_RECURSE_DIRS; 80 break; 81 case 'm': 82 /* free previously assigned value */ 83 if (logmsg != NULL) 84 xfree(logmsg); 85 logmsg = xstrdup(optarg); 86 mflag = 1; 87 break; 88 case 'R': 89 flags |= CR_RECURSE_DIRS; 90 break; 91 case 'r': 92 break; 93 default: 94 fatal("%s", cvs_cmd_commit.cmd_synopsis); 95 } 96 } 97 98 argc -= optind; 99 argv += optind; 100 101 /* -F and -m are mutually exclusive */ 102 if (Fflag && mflag) 103 fatal("cannot specify both a log file and a message"); 104 105 TAILQ_INIT(&files_affected); 106 TAILQ_INIT(&files_added); 107 TAILQ_INIT(&files_removed); 108 TAILQ_INIT(&files_modified); 109 conflicts_found = 0; 110 111 cr.enterdir = NULL; 112 cr.leavedir = NULL; 113 cr.fileproc = cvs_commit_check_files; 114 cr.flags = flags; 115 116 if (argc > 0) 117 cvs_file_run(argc, argv, &cr); 118 else 119 cvs_file_run(1, &arg, &cr); 120 121 if (conflicts_found != 0) 122 fatal("%d conflicts found, please correct these first", 123 conflicts_found); 124 125 if (TAILQ_EMPTY(&files_affected)) 126 return (0); 127 128 if (logmsg == NULL && cvs_server_active == 0) { 129 logmsg = cvs_logmsg_create(&files_added, &files_removed, 130 &files_modified); 131 } 132 133 if (logmsg == NULL) 134 fatal("This shouldnt happen, honestly!"); 135 136 cvs_file_freelist(&files_modified); 137 cvs_file_freelist(&files_removed); 138 cvs_file_freelist(&files_added); 139 140 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 141 cvs_client_connect_to_server(); 142 cr.fileproc = cvs_client_sendfile; 143 144 if (argc > 0) 145 cvs_file_run(argc, argv, &cr); 146 else 147 cvs_file_run(1, &arg, &cr); 148 149 if (!(flags & CR_RECURSE_DIRS)) 150 cvs_client_send_request("Argument -l"); 151 152 cvs_client_send_logmsg(logmsg); 153 cvs_client_send_files(argv, argc); 154 cvs_client_senddir("."); 155 cvs_client_send_request("ci"); 156 cvs_client_get_responses(); 157 } else { 158 cr.fileproc = cvs_commit_lock_dirs; 159 cvs_file_walklist(&files_affected, &cr); 160 161 cr.fileproc = cvs_commit_local; 162 cvs_file_walklist(&files_affected, &cr); 163 cvs_file_freelist(&files_affected); 164 165 cvs_get_repository_name(".", repo, MAXPATHLEN); 166 mc = cvs_module_lookup(repo); 167 if (mc->mc_prog != NULL && 168 (mc->mc_flags & MODULE_RUN_ON_COMMIT)) 169 cvs_exec(mc->mc_prog); 170 } 171 172 xfree(logmsg); 173 return (0); 174 } 175 176 void 177 cvs_commit_lock_dirs(struct cvs_file *cf) 178 { 179 char repo[MAXPATHLEN]; 180 181 cvs_get_repository_path(cf->file_wd, repo, sizeof(repo)); 182 cvs_log(LP_TRACE, "cvs_commit_lock_dirs: %s", repo); 183 184 /* locks stay in place until we are fully done and exit */ 185 cvs_repository_lock(repo, 1); 186 } 187 188 void 189 cvs_commit_check_files(struct cvs_file *cf) 190 { 191 char *tag; 192 RCSNUM *branch, *brev; 193 char rev[CVS_REV_BUFSZ]; 194 195 branch = brev = NULL; 196 197 cvs_log(LP_TRACE, "cvs_commit_check_files(%s)", cf->file_path); 198 199 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) 200 cvs_remote_classify_file(cf); 201 else 202 cvs_file_classify(cf, cvs_directory_tag); 203 204 if (cf->file_type == CVS_DIR) { 205 if (verbosity > 1) 206 cvs_log(LP_NOTICE, "Examining %s", cf->file_path); 207 return; 208 } 209 210 if (cf->file_status == FILE_CONFLICT || 211 cf->file_status == FILE_UNLINK) { 212 conflicts_found++; 213 return; 214 } 215 216 if (cf->file_status != FILE_REMOVED && 217 update_has_conflict_markers(cf)) { 218 cvs_log(LP_ERR, "conflict: unresolved conflicts in %s from " 219 "merging, please fix these first", cf->file_path); 220 conflicts_found++; 221 return; 222 } 223 224 if (cf->file_status == FILE_MERGE || 225 cf->file_status == FILE_PATCH || 226 cf->file_status == FILE_CHECKOUT || 227 cf->file_status == FILE_LOST) { 228 cvs_log(LP_ERR, "conflict: %s is not up-to-date", 229 cf->file_path); 230 conflicts_found++; 231 return; 232 } 233 234 if (cf->file_ent != NULL && cf->file_ent->ce_date != -1) { 235 cvs_log(LP_ERR, "conflict: cannot commit to sticky date for %s", 236 cf->file_path); 237 conflicts_found++; 238 return; 239 } 240 241 if (current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 242 tag = cvs_directory_tag; 243 if (cf->file_ent != NULL) 244 tag = cf->file_ent->ce_tag; 245 246 if (tag != NULL && cf->file_rcs != NULL) { 247 brev = rcs_sym_getrev(cf->file_rcs, tag); 248 if (brev != NULL) { 249 if (RCSNUM_ISBRANCH(brev)) 250 goto next; 251 rcsnum_free(brev); 252 } 253 254 brev = rcs_translate_tag(tag, cf->file_rcs); 255 256 if (brev == NULL) { 257 fatal("failed to resolve tag: %s", 258 cf->file_ent->ce_tag); 259 } 260 261 rcsnum_tostr(brev, rev, sizeof(rev)); 262 if ((branch = rcsnum_revtobr(brev)) == NULL) { 263 cvs_log(LP_ERR, "%s is not a branch revision", 264 rev); 265 conflicts_found++; 266 rcsnum_free(brev); 267 return; 268 } 269 270 if (!RCSNUM_ISBRANCHREV(brev)) { 271 cvs_log(LP_ERR, "%s is not a branch revision", 272 rev); 273 conflicts_found++; 274 rcsnum_free(branch); 275 rcsnum_free(brev); 276 return; 277 } 278 279 rcsnum_tostr(branch, rev, sizeof(rev)); 280 if (!RCSNUM_ISBRANCH(branch)) { 281 cvs_log(LP_ERR, "%s (%s) is not a branch", 282 cf->file_ent->ce_tag, rev); 283 conflicts_found++; 284 rcsnum_free(branch); 285 rcsnum_free(brev); 286 return; 287 } 288 } 289 } 290 291 next: 292 if (branch != NULL) 293 rcsnum_free(branch); 294 if (brev != NULL) 295 rcsnum_free(brev); 296 297 if (cf->file_status == FILE_ADDED || 298 cf->file_status == FILE_REMOVED || 299 cf->file_status == FILE_MODIFIED) 300 cvs_file_get(cf->file_path, 0, &files_affected); 301 302 switch (cf->file_status) { 303 case FILE_ADDED: 304 cvs_file_get(cf->file_path, 0, &files_added); 305 break; 306 case FILE_REMOVED: 307 cvs_file_get(cf->file_path, 0, &files_removed); 308 break; 309 case FILE_MODIFIED: 310 cvs_file_get(cf->file_path, 0, &files_modified); 311 break; 312 } 313 } 314 315 void 316 cvs_commit_local(struct cvs_file *cf) 317 { 318 char *tag; 319 BUF *b, *d; 320 int onbranch, isnew, histtype; 321 RCSNUM *nrev, *crev, *rrev, *brev; 322 int openflags, rcsflags; 323 char rbuf[CVS_REV_BUFSZ], nbuf[CVS_REV_BUFSZ]; 324 CVSENTRIES *entlist; 325 char attic[MAXPATHLEN], repo[MAXPATHLEN], rcsfile[MAXPATHLEN]; 326 327 cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path); 328 cvs_file_classify(cf, cvs_directory_tag); 329 330 if (cvs_noexec == 1) 331 return; 332 333 if (cf->file_type != CVS_FILE) 334 fatal("cvs_commit_local: '%s' is not a file", cf->file_path); 335 336 if (cf->file_status != FILE_MODIFIED && 337 cf->file_status != FILE_ADDED && 338 cf->file_status != FILE_REMOVED) { 339 cvs_log(LP_ERR, "skipping bogus file `%s'", cf->file_path); 340 return; 341 } 342 343 onbranch = 0; 344 nrev = RCS_HEAD_REV; 345 crev = NULL; 346 rrev = NULL; 347 348 if (cf->file_rcs != NULL && cf->file_rcs->rf_branch != NULL) { 349 rcsnum_free(cf->file_rcs->rf_branch); 350 cf->file_rcs->rf_branch = NULL; 351 } 352 353 if (cf->file_status == FILE_MODIFIED || 354 cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED 355 && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1)) { 356 rrev = rcs_head_get(cf->file_rcs); 357 crev = rcs_head_get(cf->file_rcs); 358 if (crev == NULL || rrev == NULL) 359 fatal("RCS head empty or missing in %s\n", 360 cf->file_rcs->rf_path); 361 362 tag = cvs_directory_tag; 363 if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL) 364 tag = cf->file_ent->ce_tag; 365 366 if (tag != NULL) { 367 rcsnum_free(crev); 368 crev = rcs_translate_tag(tag, cf->file_rcs); 369 if (crev == NULL) { 370 fatal("failed to resolve existing tag: %s", 371 tag); 372 } 373 374 if (RCSNUM_ISBRANCHREV(crev)) { 375 nrev = rcsnum_alloc(); 376 rcsnum_cpy(crev, nrev, 0); 377 rcsnum_inc(nrev); 378 } else if (!RCSNUM_ISBRANCH(crev)) { 379 brev = rcs_sym_getrev(cf->file_rcs, tag); 380 if (brev == NULL) 381 fatal("no more tag?"); 382 nrev = rcsnum_brtorev(brev); 383 if (nrev == NULL) 384 fatal("failed to create branch rev"); 385 rcsnum_free(brev); 386 } else { 387 fatal("this isnt suppose to happen, honestly"); 388 } 389 390 rcsnum_free(rrev); 391 rrev = rcsnum_branch_root(nrev); 392 393 /* branch stuff was checked in cvs_commit_check_files */ 394 onbranch = 1; 395 } 396 397 rcsnum_tostr(crev, rbuf, sizeof(rbuf)); 398 } else { 399 strlcpy(rbuf, "Non-existent", sizeof(rbuf)); 400 } 401 402 if (rrev != NULL) 403 rcsnum_free(rrev); 404 isnew = 0; 405 if (cf->file_status == FILE_ADDED) { 406 isnew = 1; 407 rcsflags = RCS_CREATE; 408 openflags = O_CREAT | O_TRUNC | O_WRONLY; 409 if (cf->file_rcs != NULL) { 410 if (cf->in_attic == 0) 411 cvs_log(LP_ERR, "warning: expected %s " 412 "to be in the Attic", cf->file_path); 413 414 if (cf->file_rcs->rf_dead == 0) 415 cvs_log(LP_ERR, "warning: expected %s " 416 "to be dead", cf->file_path); 417 418 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 419 (void)xsnprintf(rcsfile, MAXPATHLEN, "%s/%s%s", 420 repo, cf->file_name, RCS_FILE_EXT); 421 422 if (rename(cf->file_rpath, rcsfile) == -1) 423 fatal("cvs_commit_local: failed to move %s " 424 "outside the Attic: %s", cf->file_path, 425 strerror(errno)); 426 427 xfree(cf->file_rpath); 428 cf->file_rpath = xstrdup(rcsfile); 429 430 rcsflags = RCS_READ | RCS_PARSE_FULLY; 431 openflags = O_RDONLY; 432 rcs_close(cf->file_rcs); 433 isnew = 0; 434 } 435 436 cf->repo_fd = open(cf->file_rpath, openflags); 437 if (cf->repo_fd < 0) 438 fatal("cvs_commit_local: %s", strerror(errno)); 439 440 cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd, 441 rcsflags, 0444); 442 if (cf->file_rcs == NULL) 443 fatal("cvs_commit_local: failed to create RCS file " 444 "for %s", cf->file_path); 445 446 commit_desc_set(cf); 447 } 448 449 if (verbosity > 1) { 450 cvs_printf("Checking in %s:\n", cf->file_path); 451 cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path); 452 cvs_printf("old revision: %s; ", rbuf); 453 } 454 455 if (isnew == 0 && onbranch == 0) 456 d = commit_diff(cf, cf->file_rcs->rf_head, 0); 457 458 if (cf->file_status == FILE_REMOVED) { 459 b = rcs_rev_getbuf(cf->file_rcs, crev, 0); 460 } else if (onbranch == 1) { 461 b = commit_diff(cf, crev, 1); 462 } else { 463 b = cvs_buf_load_fd(cf->fd); 464 } 465 466 if (isnew == 0 && onbranch == 0) { 467 if (rcs_deltatext_set(cf->file_rcs, crev, d) == -1) 468 fatal("cvs_commit_local: failed to set delta"); 469 } 470 471 if (rcs_rev_add(cf->file_rcs, nrev, logmsg, -1, NULL) == -1) 472 fatal("cvs_commit_local: failed to add new revision"); 473 474 if (nrev == RCS_HEAD_REV) 475 nrev = cf->file_rcs->rf_head; 476 477 if (rcs_deltatext_set(cf->file_rcs, nrev, b) == -1) 478 fatal("cvs_commit_local: failed to set new HEAD delta"); 479 480 if (cf->file_status == FILE_REMOVED) { 481 if (rcs_state_set(cf->file_rcs, nrev, RCS_STATE_DEAD) == -1) 482 fatal("cvs_commit_local: failed to set state"); 483 } 484 485 if (cf->file_status == FILE_ADDED && cf->file_ent->ce_opts != NULL) { 486 int cf_kflag; 487 488 cf_kflag = rcs_kflag_get(cf->file_ent->ce_opts + 2); 489 rcs_kwexp_set(cf->file_rcs, cf_kflag); 490 } 491 492 rcs_write(cf->file_rcs); 493 494 if (cf->file_status == FILE_REMOVED) { 495 strlcpy(nbuf, "Removed", sizeof(nbuf)); 496 } else if (cf->file_status == FILE_ADDED) { 497 if (cf->file_rcs->rf_dead == 1) 498 strlcpy(nbuf, "Initial Revision", sizeof(nbuf)); 499 else 500 rcsnum_tostr(nrev, nbuf, sizeof(nbuf)); 501 } else if (cf->file_status == FILE_MODIFIED) { 502 rcsnum_tostr(nrev, nbuf, sizeof(nbuf)); 503 } 504 505 if (verbosity > 1) 506 cvs_printf("new revision: %s\n", nbuf); 507 508 (void)unlink(cf->file_path); 509 (void)close(cf->fd); 510 cf->fd = -1; 511 512 if (cf->file_status != FILE_REMOVED) { 513 cvs_checkout_file(cf, nrev, NULL, CO_COMMIT); 514 } else { 515 entlist = cvs_ent_open(cf->file_wd); 516 cvs_ent_remove(entlist, cf->file_name); 517 cvs_ent_close(entlist, ENT_SYNC); 518 519 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 520 521 (void)xsnprintf(attic, MAXPATHLEN, "%s/%s", 522 repo, CVS_PATH_ATTIC); 523 524 if (mkdir(attic, 0755) == -1 && errno != EEXIST) 525 fatal("cvs_commit_local: failed to create Attic"); 526 527 (void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo, 528 CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT); 529 530 if (rename(cf->file_rpath, attic) == -1) 531 fatal("cvs_commit_local: failed to move %s to Attic", 532 cf->file_path); 533 534 if (cvs_server_active == 1) 535 cvs_server_update_entry("Remove-entry", cf); 536 } 537 538 if (verbosity > 1) 539 cvs_printf("done\n"); 540 else { 541 cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s", 542 cf->file_path, rbuf, nbuf); 543 } 544 545 switch (cf->file_status) { 546 case FILE_MODIFIED: 547 histtype = CVS_HISTORY_COMMIT_MODIFIED; 548 break; 549 case FILE_ADDED: 550 histtype = CVS_HISTORY_COMMIT_ADDED; 551 break; 552 case FILE_REMOVED: 553 histtype = CVS_HISTORY_COMMIT_REMOVED; 554 break; 555 } 556 557 if (crev != NULL) 558 rcsnum_free(crev); 559 560 cvs_history_add(histtype, cf, NULL); 561 } 562 563 static BUF * 564 commit_diff(struct cvs_file *cf, RCSNUM *rev, int reverse) 565 { 566 int fd1, fd2, f; 567 char *p1, *p2, *p; 568 BUF *b; 569 570 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 571 572 if (cf->file_status == FILE_MODIFIED || 573 cf->file_status == FILE_ADDED) { 574 b = cvs_buf_load_fd(cf->fd); 575 fd1 = cvs_buf_write_stmp(b, p1, NULL); 576 cvs_buf_free(b); 577 } else { 578 fd1 = rcs_rev_write_stmp(cf->file_rcs, rev, p1, 0); 579 } 580 581 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 582 fd2 = rcs_rev_write_stmp(cf->file_rcs, rev, p2, RCS_KWEXP_NONE); 583 584 b = cvs_buf_alloc(128); 585 586 diff_format = D_RCSDIFF; 587 588 if (reverse == 1) { 589 p = p1; 590 p1 = p2; 591 p2 = p; 592 593 f = fd1; 594 fd1 = fd2; 595 fd2 = f; 596 } 597 598 if (cvs_diffreg(p1, p2, fd1, fd2, b) == D_ERROR) 599 fatal("commit_diff: failed to get RCS patch"); 600 601 close(fd1); 602 close(fd2); 603 604 xfree(p1); 605 xfree(p2); 606 607 return (b); 608 } 609 610 static void 611 commit_desc_set(struct cvs_file *cf) 612 { 613 BUF *bp; 614 int fd; 615 char desc_path[MAXPATHLEN], *desc; 616 617 (void)xsnprintf(desc_path, MAXPATHLEN, "%s/%s%s", 618 CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT); 619 620 if ((fd = open(desc_path, O_RDONLY)) == -1) 621 return; 622 623 bp = cvs_buf_load_fd(fd); 624 cvs_buf_putc(bp, '\0'); 625 desc = cvs_buf_release(bp); 626 627 rcs_desc_set(cf->file_rcs, desc); 628 629 (void)close(fd); 630 (void)cvs_unlink(desc_path); 631 632 xfree(desc); 633 } 634