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