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