1 /* $OpenBSD: commit.c,v 1.133 2008/05/17 21:06:44 tobias 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 rcsnum_free(rrev); 369 brev = rcs_sym_getrev(cf->file_rcs, tag); 370 crev = rcs_translate_tag(tag, cf->file_rcs); 371 if (brev == NULL || crev == NULL) { 372 fatal("failed to resolve existing tag: %s", 373 tag); 374 } 375 376 rrev = rcsnum_alloc(); 377 rcsnum_cpy(brev, rrev, brev->rn_len - 1); 378 379 if (RCSNUM_ISBRANCHREV(crev) && 380 rcsnum_cmp(crev, rrev, 0)) { 381 nrev = rcsnum_alloc(); 382 rcsnum_cpy(crev, nrev, 0); 383 rcsnum_inc(nrev); 384 } else if (!RCSNUM_ISBRANCH(crev)) { 385 nrev = rcsnum_brtorev(brev); 386 if (nrev == NULL) 387 fatal("failed to create branch rev"); 388 } else { 389 fatal("this isnt suppose to happen, honestly"); 390 } 391 392 rcsnum_free(brev); 393 rcsnum_free(rrev); 394 rrev = rcsnum_branch_root(nrev); 395 396 /* branch stuff was checked in cvs_commit_check_files */ 397 onbranch = 1; 398 } 399 400 rcsnum_tostr(crev, rbuf, sizeof(rbuf)); 401 } else { 402 strlcpy(rbuf, "Non-existent", sizeof(rbuf)); 403 } 404 405 if (rrev != NULL) 406 rcsnum_free(rrev); 407 isnew = 0; 408 if (cf->file_status == FILE_ADDED) { 409 isnew = 1; 410 rcsflags = RCS_CREATE; 411 openflags = O_CREAT | O_TRUNC | O_WRONLY; 412 if (cf->file_rcs != NULL) { 413 if (cf->in_attic == 0) 414 cvs_log(LP_ERR, "warning: expected %s " 415 "to be in the Attic", cf->file_path); 416 417 if (cf->file_rcs->rf_dead == 0) 418 cvs_log(LP_ERR, "warning: expected %s " 419 "to be dead", cf->file_path); 420 421 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 422 (void)xsnprintf(rcsfile, MAXPATHLEN, "%s/%s%s", 423 repo, cf->file_name, RCS_FILE_EXT); 424 425 if (rename(cf->file_rpath, rcsfile) == -1) 426 fatal("cvs_commit_local: failed to move %s " 427 "outside the Attic: %s", cf->file_path, 428 strerror(errno)); 429 430 xfree(cf->file_rpath); 431 cf->file_rpath = xstrdup(rcsfile); 432 433 rcsflags = RCS_READ | RCS_PARSE_FULLY; 434 openflags = O_RDONLY; 435 rcs_close(cf->file_rcs); 436 isnew = 0; 437 } 438 439 cf->repo_fd = open(cf->file_rpath, openflags); 440 if (cf->repo_fd < 0) 441 fatal("cvs_commit_local: %s", strerror(errno)); 442 443 cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd, 444 rcsflags, 0444); 445 if (cf->file_rcs == NULL) 446 fatal("cvs_commit_local: failed to create RCS file " 447 "for %s", cf->file_path); 448 449 commit_desc_set(cf); 450 } 451 452 if (verbosity > 1) { 453 cvs_printf("Checking in %s:\n", cf->file_path); 454 cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path); 455 cvs_printf("old revision: %s; ", rbuf); 456 } 457 458 if (isnew == 0 && onbranch == 0) 459 d = commit_diff(cf, cf->file_rcs->rf_head, 0); 460 461 if (cf->file_status == FILE_REMOVED) { 462 b = rcs_rev_getbuf(cf->file_rcs, crev, 0); 463 } else if (onbranch == 1) { 464 b = commit_diff(cf, crev, 1); 465 } else { 466 b = cvs_buf_load_fd(cf->fd); 467 } 468 469 if (isnew == 0 && onbranch == 0) { 470 if (rcs_deltatext_set(cf->file_rcs, crev, d) == -1) 471 fatal("cvs_commit_local: failed to set delta"); 472 } 473 474 if (rcs_rev_add(cf->file_rcs, nrev, logmsg, -1, NULL) == -1) 475 fatal("cvs_commit_local: failed to add new revision"); 476 477 if (nrev == RCS_HEAD_REV) 478 nrev = cf->file_rcs->rf_head; 479 480 if (rcs_deltatext_set(cf->file_rcs, nrev, b) == -1) 481 fatal("cvs_commit_local: failed to set new HEAD delta"); 482 483 if (cf->file_status == FILE_REMOVED) { 484 if (rcs_state_set(cf->file_rcs, nrev, RCS_STATE_DEAD) == -1) 485 fatal("cvs_commit_local: failed to set state"); 486 } 487 488 if (cf->file_status == FILE_ADDED && cf->file_ent->ce_opts != NULL) { 489 int cf_kflag; 490 491 cf_kflag = rcs_kflag_get(cf->file_ent->ce_opts + 2); 492 rcs_kwexp_set(cf->file_rcs, cf_kflag); 493 } 494 495 rcs_write(cf->file_rcs); 496 497 if (cf->file_status == FILE_REMOVED) { 498 strlcpy(nbuf, "Removed", sizeof(nbuf)); 499 } else if (cf->file_status == FILE_ADDED) { 500 if (cf->file_rcs->rf_dead == 1) 501 strlcpy(nbuf, "Initial Revision", sizeof(nbuf)); 502 else 503 rcsnum_tostr(nrev, nbuf, sizeof(nbuf)); 504 } else if (cf->file_status == FILE_MODIFIED) { 505 rcsnum_tostr(nrev, nbuf, sizeof(nbuf)); 506 } 507 508 if (verbosity > 1) 509 cvs_printf("new revision: %s\n", nbuf); 510 511 (void)unlink(cf->file_path); 512 (void)close(cf->fd); 513 cf->fd = -1; 514 515 if (cf->file_status != FILE_REMOVED) { 516 cvs_checkout_file(cf, nrev, NULL, CO_COMMIT); 517 } else { 518 entlist = cvs_ent_open(cf->file_wd); 519 cvs_ent_remove(entlist, cf->file_name); 520 cvs_ent_close(entlist, ENT_SYNC); 521 522 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 523 524 (void)xsnprintf(attic, MAXPATHLEN, "%s/%s", 525 repo, CVS_PATH_ATTIC); 526 527 if (mkdir(attic, 0755) == -1 && errno != EEXIST) 528 fatal("cvs_commit_local: failed to create Attic"); 529 530 (void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo, 531 CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT); 532 533 if (rename(cf->file_rpath, attic) == -1) 534 fatal("cvs_commit_local: failed to move %s to Attic", 535 cf->file_path); 536 537 if (cvs_server_active == 1) 538 cvs_server_update_entry("Remove-entry", cf); 539 } 540 541 if (verbosity > 1) 542 cvs_printf("done\n"); 543 else { 544 cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s", 545 cf->file_path, rbuf, nbuf); 546 } 547 548 switch (cf->file_status) { 549 case FILE_MODIFIED: 550 histtype = CVS_HISTORY_COMMIT_MODIFIED; 551 break; 552 case FILE_ADDED: 553 histtype = CVS_HISTORY_COMMIT_ADDED; 554 break; 555 case FILE_REMOVED: 556 histtype = CVS_HISTORY_COMMIT_REMOVED; 557 break; 558 } 559 560 if (crev != NULL) 561 rcsnum_free(crev); 562 563 cvs_history_add(histtype, cf, NULL); 564 } 565 566 static BUF * 567 commit_diff(struct cvs_file *cf, RCSNUM *rev, int reverse) 568 { 569 int fd1, fd2, f; 570 char *p1, *p2, *p; 571 BUF *b; 572 573 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 574 575 if (cf->file_status == FILE_MODIFIED || 576 cf->file_status == FILE_ADDED) { 577 b = cvs_buf_load_fd(cf->fd); 578 fd1 = cvs_buf_write_stmp(b, p1, NULL); 579 cvs_buf_free(b); 580 } else { 581 fd1 = rcs_rev_write_stmp(cf->file_rcs, rev, p1, 0); 582 } 583 584 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 585 fd2 = rcs_rev_write_stmp(cf->file_rcs, rev, p2, RCS_KWEXP_NONE); 586 587 b = cvs_buf_alloc(128); 588 589 diff_format = D_RCSDIFF; 590 591 if (reverse == 1) { 592 p = p1; 593 p1 = p2; 594 p2 = p; 595 596 f = fd1; 597 fd1 = fd2; 598 fd2 = f; 599 } 600 601 if (cvs_diffreg(p1, p2, fd1, fd2, b) == D_ERROR) 602 fatal("commit_diff: failed to get RCS patch"); 603 604 close(fd1); 605 close(fd2); 606 607 xfree(p1); 608 xfree(p2); 609 610 return (b); 611 } 612 613 static void 614 commit_desc_set(struct cvs_file *cf) 615 { 616 BUF *bp; 617 int fd; 618 char desc_path[MAXPATHLEN], *desc; 619 620 (void)xsnprintf(desc_path, MAXPATHLEN, "%s/%s%s", 621 CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT); 622 623 if ((fd = open(desc_path, O_RDONLY)) == -1) 624 return; 625 626 bp = cvs_buf_load_fd(fd); 627 cvs_buf_putc(bp, '\0'); 628 desc = cvs_buf_release(bp); 629 630 rcs_desc_set(cf->file_rcs, desc); 631 632 (void)close(fd); 633 (void)cvs_unlink(desc_path); 634 635 xfree(desc); 636 } 637