1 /* $OpenBSD: commit.c,v 1.127 2008/02/04 22:36:40 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 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 (current_cvsroot->cr_method == CVS_METHOD_LOCAL) { 219 tag = cvs_directory_tag; 220 if (cf->file_ent != NULL) 221 tag = cf->file_ent->ce_tag; 222 223 if (tag != NULL && cf->file_rcs != NULL) { 224 brev = rcs_sym_getrev(cf->file_rcs, tag); 225 if (brev != NULL) { 226 if (RCSNUM_ISBRANCH(brev)) 227 goto next; 228 rcsnum_free(brev); 229 } 230 231 brev = rcs_translate_tag(tag, cf->file_rcs); 232 233 if (brev == NULL) { 234 fatal("failed to resolve tag: %s", 235 cf->file_ent->ce_tag); 236 } 237 238 rcsnum_tostr(brev, rev, sizeof(rev)); 239 if ((branch = rcsnum_revtobr(brev)) == NULL) { 240 cvs_log(LP_ERR, "%s is not a branch revision", 241 rev); 242 conflicts_found++; 243 rcsnum_free(brev); 244 return; 245 } 246 247 if (!RCSNUM_ISBRANCHREV(brev)) { 248 cvs_log(LP_ERR, "%s is not a branch revision", 249 rev); 250 conflicts_found++; 251 rcsnum_free(branch); 252 rcsnum_free(brev); 253 return; 254 } 255 256 rcsnum_tostr(branch, rev, sizeof(rev)); 257 if (!RCSNUM_ISBRANCH(branch)) { 258 cvs_log(LP_ERR, "%s (%s) is not a branch", 259 cf->file_ent->ce_tag, rev); 260 conflicts_found++; 261 rcsnum_free(branch); 262 rcsnum_free(brev); 263 return; 264 } 265 } 266 } 267 268 next: 269 if (branch != NULL) 270 rcsnum_free(branch); 271 if (brev != NULL) 272 rcsnum_free(brev); 273 274 if (cf->file_status == FILE_ADDED || 275 cf->file_status == FILE_REMOVED || 276 cf->file_status == FILE_MODIFIED) 277 cvs_file_get(cf->file_path, 0, &files_affected); 278 279 switch (cf->file_status) { 280 case FILE_ADDED: 281 cvs_file_get(cf->file_path, 0, &files_added); 282 break; 283 case FILE_REMOVED: 284 cvs_file_get(cf->file_path, 0, &files_removed); 285 break; 286 case FILE_MODIFIED: 287 cvs_file_get(cf->file_path, 0, &files_modified); 288 break; 289 } 290 } 291 292 void 293 cvs_commit_local(struct cvs_file *cf) 294 { 295 char *tag; 296 BUF *b, *d; 297 int onbranch, isnew, histtype; 298 RCSNUM *nrev, *crev, *rrev, *brev; 299 int openflags, rcsflags; 300 char rbuf[CVS_REV_BUFSZ], nbuf[CVS_REV_BUFSZ]; 301 CVSENTRIES *entlist; 302 char attic[MAXPATHLEN], repo[MAXPATHLEN], rcsfile[MAXPATHLEN]; 303 304 cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path); 305 cvs_file_classify(cf, cvs_directory_tag); 306 307 if (cvs_noexec == 1) 308 return; 309 310 if (cf->file_type != CVS_FILE) 311 fatal("cvs_commit_local: '%s' is not a file", cf->file_path); 312 313 if (cf->file_status != FILE_MODIFIED && 314 cf->file_status != FILE_ADDED && 315 cf->file_status != FILE_REMOVED) { 316 cvs_log(LP_ERR, "skipping bogus file `%s'", cf->file_path); 317 return; 318 } 319 320 onbranch = 0; 321 nrev = RCS_HEAD_REV; 322 crev = NULL; 323 rrev = NULL; 324 325 if (cf->file_rcs != NULL && cf->file_rcs->rf_branch != NULL) { 326 rcsnum_free(cf->file_rcs->rf_branch); 327 cf->file_rcs->rf_branch = NULL; 328 } 329 330 if (cf->file_status == FILE_MODIFIED || 331 cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED 332 && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1)) { 333 rrev = rcs_head_get(cf->file_rcs); 334 crev = rcs_head_get(cf->file_rcs); 335 if (crev == NULL || rrev == NULL) 336 fatal("RCS head empty or missing in %s\n", 337 cf->file_rcs->rf_path); 338 339 tag = cvs_directory_tag; 340 if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL) 341 tag = cf->file_ent->ce_tag; 342 343 if (tag != NULL) { 344 rcsnum_free(crev); 345 crev = rcs_translate_tag(tag, cf->file_rcs); 346 if (crev == NULL) { 347 fatal("failed to resolve existing tag: %s", 348 tag); 349 } 350 351 if (RCSNUM_ISBRANCHREV(crev)) { 352 nrev = rcsnum_alloc(); 353 rcsnum_cpy(crev, nrev, 0); 354 rcsnum_inc(nrev); 355 } else if (!RCSNUM_ISBRANCH(crev)) { 356 brev = rcs_sym_getrev(cf->file_rcs, tag); 357 if (brev == NULL) 358 fatal("no more tag?"); 359 nrev = rcsnum_brtorev(brev); 360 if (nrev == NULL) 361 fatal("failed to create branch rev"); 362 rcsnum_free(brev); 363 } else { 364 fatal("this isnt suppose to happen, honestly"); 365 } 366 367 rcsnum_free(rrev); 368 rrev = rcsnum_branch_root(nrev); 369 370 /* branch stuff was checked in cvs_commit_check_files */ 371 onbranch = 1; 372 } 373 374 rcsnum_tostr(crev, rbuf, sizeof(rbuf)); 375 } else { 376 strlcpy(rbuf, "Non-existent", sizeof(rbuf)); 377 } 378 379 if (rrev != NULL) 380 rcsnum_free(rrev); 381 isnew = 0; 382 if (cf->file_status == FILE_ADDED) { 383 isnew = 1; 384 rcsflags = RCS_CREATE; 385 openflags = O_CREAT | O_TRUNC | O_WRONLY; 386 if (cf->file_rcs != NULL) { 387 if (cf->in_attic == 0) 388 cvs_log(LP_ERR, "warning: expected %s " 389 "to be in the Attic", cf->file_path); 390 391 if (cf->file_rcs->rf_dead == 0) 392 cvs_log(LP_ERR, "warning: expected %s " 393 "to be dead", cf->file_path); 394 395 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 396 (void)xsnprintf(rcsfile, MAXPATHLEN, "%s/%s%s", 397 repo, cf->file_name, RCS_FILE_EXT); 398 399 if (rename(cf->file_rpath, rcsfile) == -1) 400 fatal("cvs_commit_local: failed to move %s " 401 "outside the Attic: %s", cf->file_path, 402 strerror(errno)); 403 404 xfree(cf->file_rpath); 405 cf->file_rpath = xstrdup(rcsfile); 406 407 rcsflags = RCS_READ | RCS_PARSE_FULLY; 408 openflags = O_RDONLY; 409 rcs_close(cf->file_rcs); 410 isnew = 0; 411 } 412 413 cf->repo_fd = open(cf->file_rpath, openflags); 414 if (cf->repo_fd < 0) 415 fatal("cvs_commit_local: %s", strerror(errno)); 416 417 cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd, 418 rcsflags, 0444); 419 if (cf->file_rcs == NULL) 420 fatal("cvs_commit_local: failed to create RCS file " 421 "for %s", cf->file_path); 422 423 commit_desc_set(cf); 424 } 425 426 if (verbosity > 1) { 427 cvs_printf("Checking in %s:\n", cf->file_path); 428 cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path); 429 cvs_printf("old revision: %s; ", rbuf); 430 } 431 432 if (isnew == 0 && onbranch == 0) 433 d = commit_diff(cf, cf->file_rcs->rf_head, 0); 434 435 if (cf->file_status == FILE_REMOVED) { 436 b = rcs_rev_getbuf(cf->file_rcs, crev, 0); 437 if (b == NULL) 438 fatal("cvs_commit_local: failed to get crev"); 439 } else if (onbranch == 1) { 440 b = commit_diff(cf, crev, 1); 441 } else { 442 if ((b = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL) 443 fatal("cvs_commit_local: failed to load file"); 444 } 445 446 if (isnew == 0 && onbranch == 0) { 447 if (rcs_deltatext_set(cf->file_rcs, crev, d) == -1) 448 fatal("cvs_commit_local: failed to set delta"); 449 } 450 451 if (rcs_rev_add(cf->file_rcs, nrev, logmsg, -1, NULL) == -1) 452 fatal("cvs_commit_local: failed to add new revision"); 453 454 if (nrev == RCS_HEAD_REV) 455 nrev = cf->file_rcs->rf_head; 456 457 if (rcs_deltatext_set(cf->file_rcs, nrev, b) == -1) 458 fatal("cvs_commit_local: failed to set new HEAD delta"); 459 460 if (cf->file_status == FILE_REMOVED) { 461 if (rcs_state_set(cf->file_rcs, nrev, RCS_STATE_DEAD) == -1) 462 fatal("cvs_commit_local: failed to set state"); 463 } 464 465 if (cf->file_status == FILE_ADDED && cf->file_ent->ce_opts != NULL) { 466 int cf_kflag; 467 468 cf_kflag = rcs_kflag_get(cf->file_ent->ce_opts + 2); 469 rcs_kwexp_set(cf->file_rcs, cf_kflag); 470 } 471 472 rcs_write(cf->file_rcs); 473 474 if (cf->file_status == FILE_REMOVED) { 475 strlcpy(nbuf, "Removed", sizeof(nbuf)); 476 } else if (cf->file_status == FILE_ADDED) { 477 if (cf->file_rcs->rf_dead == 1) 478 strlcpy(nbuf, "Initial Revision", sizeof(nbuf)); 479 else 480 rcsnum_tostr(nrev, nbuf, sizeof(nbuf)); 481 } else if (cf->file_status == FILE_MODIFIED) { 482 rcsnum_tostr(nrev, nbuf, sizeof(nbuf)); 483 } 484 485 if (verbosity > 1) 486 cvs_printf("new revision: %s\n", nbuf); 487 488 (void)unlink(cf->file_path); 489 (void)close(cf->fd); 490 cf->fd = -1; 491 492 if (cf->file_status != FILE_REMOVED) { 493 cvs_checkout_file(cf, nrev, NULL, CO_COMMIT); 494 } else { 495 entlist = cvs_ent_open(cf->file_wd); 496 cvs_ent_remove(entlist, cf->file_name); 497 cvs_ent_close(entlist, ENT_SYNC); 498 499 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 500 501 (void)xsnprintf(attic, MAXPATHLEN, "%s/%s", 502 repo, CVS_PATH_ATTIC); 503 504 if (mkdir(attic, 0755) == -1 && errno != EEXIST) 505 fatal("cvs_commit_local: failed to create Attic"); 506 507 (void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo, 508 CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT); 509 510 if (rename(cf->file_rpath, attic) == -1) 511 fatal("cvs_commit_local: failed to move %s to Attic", 512 cf->file_path); 513 514 if (cvs_server_active == 1) 515 cvs_server_update_entry("Remove-entry", cf); 516 } 517 518 if (verbosity > 1) 519 cvs_printf("done\n"); 520 else { 521 cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s", 522 cf->file_path, rbuf, nbuf); 523 } 524 525 switch (cf->file_status) { 526 case FILE_MODIFIED: 527 histtype = CVS_HISTORY_COMMIT_MODIFIED; 528 break; 529 case FILE_ADDED: 530 histtype = CVS_HISTORY_COMMIT_ADDED; 531 break; 532 case FILE_REMOVED: 533 histtype = CVS_HISTORY_COMMIT_REMOVED; 534 break; 535 } 536 537 if (crev != NULL) 538 rcsnum_free(crev); 539 540 cvs_history_add(histtype, cf, NULL); 541 } 542 543 static BUF * 544 commit_diff(struct cvs_file *cf, RCSNUM *rev, int reverse) 545 { 546 char *p1, *p2, *p; 547 BUF *b; 548 549 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 550 551 if (cf->file_status == FILE_MODIFIED || 552 cf->file_status == FILE_ADDED) { 553 if ((b = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL) 554 fatal("commit_diff: failed to load '%s'", 555 cf->file_path); 556 cvs_buf_write_stmp(b, p1, NULL); 557 cvs_buf_free(b); 558 } else { 559 rcs_rev_write_stmp(cf->file_rcs, rev, p1, 0); 560 } 561 562 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 563 rcs_rev_write_stmp(cf->file_rcs, rev, p2, RCS_KWEXP_NONE); 564 565 if ((b = cvs_buf_alloc(128, BUF_AUTOEXT)) == NULL) 566 fatal("commit_diff: failed to create diff buf"); 567 568 diff_format = D_RCSDIFF; 569 570 if (reverse == 1) { 571 p = p1; 572 p1 = p2; 573 p2 = p; 574 } 575 576 if (cvs_diffreg(p1, p2, b) == D_ERROR) 577 fatal("commit_diff: failed to get RCS patch"); 578 579 xfree(p1); 580 xfree(p2); 581 582 return (b); 583 } 584 585 static void 586 commit_desc_set(struct cvs_file *cf) 587 { 588 BUF *bp; 589 int fd; 590 char desc_path[MAXPATHLEN], *desc; 591 592 (void)xsnprintf(desc_path, MAXPATHLEN, "%s/%s%s", 593 CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT); 594 595 if ((fd = open(desc_path, O_RDONLY)) == -1) 596 return; 597 598 bp = cvs_buf_load_fd(fd, BUF_AUTOEXT); 599 cvs_buf_putc(bp, '\0'); 600 desc = cvs_buf_release(bp); 601 602 rcs_desc_set(cf->file_rcs, desc); 603 604 (void)close(fd); 605 (void)cvs_unlink(desc_path); 606 607 xfree(desc); 608 } 609