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