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