1 /* $OpenBSD: commit.c,v 1.81 2006/07/08 09:25:44 ray 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 "includes.h" 20 21 #include "cvs.h" 22 #include "diff.h" 23 #include "log.h" 24 #include "remote.h" 25 26 void cvs_commit_local(struct cvs_file *); 27 void cvs_commit_check_conflicts(struct cvs_file *); 28 29 static char *commit_diff_file(struct cvs_file *); 30 static void commit_desc_set(struct cvs_file *); 31 32 struct cvs_flisthead files_affected; 33 int conflicts_found; 34 char *logmsg; 35 36 struct cvs_cmd cvs_cmd_commit = { 37 CVS_OP_COMMIT, 0, "commit", 38 { "ci", "com" }, 39 "Check files into the repository", 40 "[-flR] [-F logfile | -m msg] [-r rev] ...", 41 "F:flm:Rr:", 42 NULL, 43 cvs_commit 44 }; 45 46 int 47 cvs_commit(int argc, char **argv) 48 { 49 int ch; 50 BUF *bp; 51 char *arg = "."; 52 int flags; 53 struct cvs_recursion cr; 54 55 flags = CR_RECURSE_DIRS; 56 57 while ((ch = getopt(argc, argv, cvs_cmd_commit.cmd_opts)) != -1) { 58 switch (ch) { 59 case 'f': 60 break; 61 case 'F': 62 bp = cvs_buf_load(optarg, BUF_AUTOEXT); 63 if (bp == NULL) 64 fatal("failed to load commit message %s", 65 optarg); 66 cvs_buf_putc(bp, '\0'); 67 logmsg = cvs_buf_release(bp); 68 break; 69 case 'l': 70 flags &= ~CR_RECURSE_DIRS; 71 break; 72 case 'm': 73 logmsg = xstrdup(optarg); 74 break; 75 case 'r': 76 break; 77 case 'R': 78 break; 79 default: 80 fatal("%s", cvs_cmd_commit.cmd_synopsis); 81 } 82 } 83 84 argc -= optind; 85 argv += optind; 86 87 if (logmsg == NULL) 88 fatal("please use -m or -F to specify a log message for now"); 89 90 if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) { 91 cr.enterdir = NULL; 92 cr.leavedir = NULL; 93 cr.fileproc = cvs_client_sendfile; 94 cr.flags = flags; 95 96 if (argc > 0) 97 cvs_file_run(argc, argv, &cr); 98 else 99 cvs_file_run(1, &arg, &cr); 100 101 cvs_client_send_request("Argument -m%s", logmsg); 102 103 cvs_client_send_files(argv, argc); 104 cvs_client_senddir("."); 105 cvs_client_send_request("ci"); 106 cvs_client_get_responses(); 107 return (0); 108 } 109 110 TAILQ_INIT(&files_affected); 111 conflicts_found = 0; 112 113 cr.enterdir = NULL; 114 cr.leavedir = NULL; 115 cr.fileproc = cvs_commit_check_conflicts; 116 cr.flags = flags; 117 118 if (argc > 0) 119 cvs_file_run(argc, argv, &cr); 120 else 121 cvs_file_run(1, &arg, &cr); 122 123 if (conflicts_found != 0) 124 fatal("%d conflicts found, please correct these first", 125 conflicts_found); 126 127 cr.fileproc = cvs_commit_local; 128 cvs_file_walklist(&files_affected, &cr); 129 cvs_file_freelist(&files_affected); 130 131 return (0); 132 } 133 134 void 135 cvs_commit_check_conflicts(struct cvs_file *cf) 136 { 137 cvs_log(LP_TRACE, "cvs_commit_check_conflicts(%s)", cf->file_path); 138 139 /* 140 * cvs_file_classify makes the noise for us 141 * XXX - we want that? 142 */ 143 cvs_file_classify(cf, NULL, 1); 144 145 if (cf->file_type == CVS_DIR) { 146 if (verbosity > 1) 147 cvs_log(LP_NOTICE, "Examining %s", cf->file_path); 148 return; 149 } 150 151 if (cf->file_status == FILE_CONFLICT || 152 cf->file_status == FILE_UNLINK) { 153 conflicts_found++; 154 return; 155 } 156 157 if (cf->file_status != FILE_REMOVED && 158 update_has_conflict_markers(cf)) { 159 cvs_log(LP_ERR, "conflict: unresolved conflicts in %s from " 160 "merging, please fix these first", cf->file_path); 161 conflicts_found++; 162 return; 163 } 164 165 if (cf->file_status == FILE_MERGE || 166 cf->file_status == FILE_PATCH || 167 cf->file_status == FILE_CHECKOUT || 168 cf->file_status == FILE_LOST) { 169 cvs_log(LP_ERR, "conflict: %s is not up-to-date", 170 cf->file_path); 171 conflicts_found++; 172 return; 173 } 174 175 if (cf->file_status == FILE_ADDED || 176 cf->file_status == FILE_REMOVED || 177 cf->file_status == FILE_MODIFIED) 178 cvs_file_get(cf->file_path, &files_affected); 179 } 180 181 void 182 cvs_commit_local(struct cvs_file *cf) 183 { 184 BUF *b; 185 int isnew; 186 int l, openflags, rcsflags; 187 char *d, *f, rbuf[24], nbuf[24]; 188 CVSENTRIES *entlist; 189 char *attic, *repo, *rcsfile; 190 191 cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path); 192 cvs_file_classify(cf, NULL, 0); 193 194 if (cvs_noexec == 1) 195 return; 196 197 if (cf->file_type != CVS_FILE) 198 fatal("cvs_commit_local: '%s' is not a file", cf->file_path); 199 200 if (cf->file_status == FILE_MODIFIED || 201 cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED 202 && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1)) 203 rcsnum_tostr(rcs_head_get(cf->file_rcs), rbuf, sizeof(rbuf)); 204 else 205 strlcpy(rbuf, "Non-existent", sizeof(rbuf)); 206 207 isnew = 0; 208 if (cf->file_status == FILE_ADDED) { 209 isnew = 1; 210 rcsflags = RCS_CREATE; 211 openflags = O_CREAT | O_TRUNC | O_WRONLY; 212 if (cf->file_rcs != NULL) { 213 if (cf->file_rcs->rf_inattic == 0) 214 cvs_log(LP_ERR, "warning: expected %s " 215 "to be in the Attic", cf->file_path); 216 217 if (cf->file_rcs->rf_dead == 0) 218 cvs_log(LP_ERR, "warning: expected %s " 219 "to be dead", cf->file_path); 220 221 rcsfile = xmalloc(MAXPATHLEN); 222 repo = xmalloc(MAXPATHLEN); 223 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 224 l = snprintf(rcsfile, MAXPATHLEN, "%s/%s%s", 225 repo, cf->file_name, RCS_FILE_EXT); 226 if (l == -1 || l >= MAXPATHLEN) 227 fatal("cvs_commit_local: overflow"); 228 229 if (rename(cf->file_rpath, rcsfile) == -1) 230 fatal("cvs_commit_local: failed to move %s " 231 "outside the Attic: %s", cf->file_path, 232 strerror(errno)); 233 234 xfree(cf->file_rpath); 235 cf->file_rpath = xstrdup(rcsfile); 236 xfree(rcsfile); 237 xfree(repo); 238 239 rcsflags = RCS_READ | RCS_PARSE_FULLY; 240 openflags = O_RDONLY; 241 rcs_close(cf->file_rcs); 242 isnew = 0; 243 } 244 245 cf->repo_fd = open(cf->file_rpath, openflags); 246 if (cf->repo_fd < 0) 247 fatal("cvs_commit_local: %s", strerror(errno)); 248 249 cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd, 250 rcsflags, 0600); 251 if (cf->file_rcs == NULL) 252 fatal("cvs_commit_local: failed to create RCS file " 253 "for %s", cf->file_path); 254 255 commit_desc_set(cf); 256 } 257 258 if (verbosity > 1) { 259 cvs_printf("Checking in %s:\n", cf->file_path); 260 cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path); 261 cvs_printf("old revision: %s; ", rbuf); 262 } 263 264 if (isnew == 0) 265 d = commit_diff_file(cf); 266 267 if (cf->file_status == FILE_REMOVED) { 268 b = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head); 269 if (b == NULL) 270 fatal("cvs_commit_local: failed to get HEAD"); 271 } else { 272 if ((b = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL) 273 fatal("cvs_commit_local: failed to load file"); 274 } 275 276 cvs_buf_putc(b, '\0'); 277 f = cvs_buf_release(b); 278 279 if (isnew == 0) { 280 if (rcs_deltatext_set(cf->file_rcs, 281 cf->file_rcs->rf_head, d) == -1) 282 fatal("cvs_commit_local: failed to set delta"); 283 } 284 285 if (rcs_rev_add(cf->file_rcs, RCS_HEAD_REV, logmsg, -1, NULL) == -1) 286 fatal("cvs_commit_local: failed to add new revision"); 287 288 if (rcs_deltatext_set(cf->file_rcs, cf->file_rcs->rf_head, f) == -1) 289 fatal("cvs_commit_local: failed to set new HEAD delta"); 290 291 xfree(f); 292 293 if (isnew == 0) 294 xfree(d); 295 296 if (cf->file_status == FILE_REMOVED) { 297 if (rcs_state_set(cf->file_rcs, 298 cf->file_rcs->rf_head, RCS_STATE_DEAD) == -1) 299 fatal("cvs_commit_local: failed to set state"); 300 } 301 302 if (cf->file_rcs->rf_branch != NULL) { 303 rcsnum_free(cf->file_rcs->rf_branch); 304 cf->file_rcs->rf_branch = NULL; 305 } 306 307 rcs_write(cf->file_rcs); 308 309 if (cf->file_status == FILE_REMOVED) { 310 strlcpy(nbuf, "Removed", sizeof(nbuf)); 311 } else if (cf->file_status == FILE_ADDED) { 312 if (cf->file_rcs->rf_dead == 1) 313 strlcpy(nbuf, "Initial Revision", sizeof(nbuf)); 314 else 315 rcsnum_tostr(cf->file_rcs->rf_head, 316 nbuf, sizeof(nbuf)); 317 } else if (cf->file_status == FILE_MODIFIED) { 318 rcsnum_tostr(cf->file_rcs->rf_head, nbuf, sizeof(nbuf)); 319 } 320 321 if (verbosity > 1) 322 cvs_printf("new revision: %s\n", nbuf); 323 324 (void)unlink(cf->file_path); 325 (void)close(cf->fd); 326 cf->fd = -1; 327 328 if (cf->file_status != FILE_REMOVED) { 329 b = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head); 330 if (b == NULL) 331 fatal("cvs_commit_local: failed to get HEAD"); 332 333 cvs_checkout_file(cf, cf->file_rcs->rf_head, b, 0); 334 } else { 335 entlist = cvs_ent_open(cf->file_wd); 336 cvs_ent_remove(entlist, cf->file_name); 337 cvs_ent_close(entlist, ENT_SYNC); 338 339 repo = xmalloc(MAXPATHLEN); 340 attic = xmalloc(MAXPATHLEN); 341 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 342 343 l = snprintf(attic, MAXPATHLEN, "%s/%s", repo, CVS_PATH_ATTIC); 344 if (l == -1 || l >= MAXPATHLEN) 345 fatal("cvs_commit_local: overflow"); 346 347 if (mkdir(attic, 0755) == -1 && errno != EEXIST) 348 fatal("cvs_commit_local: failed to create Attic"); 349 350 l = snprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo, 351 CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT); 352 if (l == -1 || l >= MAXPATHLEN) 353 fatal("cvs_commit_local: overflow"); 354 355 if (rename(cf->file_rpath, attic) == -1) 356 fatal("cvs_commit_local: failed to move %s to Attic", 357 cf->file_path); 358 359 xfree(repo); 360 xfree(attic); 361 } 362 363 if (verbosity > 1) 364 cvs_printf("done\n"); 365 else { 366 cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s", 367 cf->file_path, rbuf, nbuf); 368 } 369 } 370 371 static char * 372 commit_diff_file(struct cvs_file *cf) 373 { 374 char*delta, *p1, *p2; 375 BUF *b1, *b2, *b3; 376 377 if (cf->file_status == FILE_MODIFIED || 378 cf->file_status == FILE_ADDED) { 379 if ((b1 = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL) 380 fatal("commit_diff_file: failed to load '%s'", 381 cf->file_path); 382 } else { 383 b1 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head); 384 if (b1 == NULL) 385 fatal("commit_diff_file: failed to load HEAD"); 386 b1 = rcs_kwexp_buf(b1, cf->file_rcs, cf->file_rcs->rf_head); 387 } 388 389 if ((b2 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head)) == NULL) 390 fatal("commit_diff_file: failed to load HEAD for '%s'", 391 cf->file_path); 392 393 if ((b3 = cvs_buf_alloc(128, BUF_AUTOEXT)) == NULL) 394 fatal("commit_diff_file: failed to create diff buf"); 395 396 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 397 cvs_buf_write_stmp(b1, p1, NULL); 398 cvs_buf_free(b1); 399 400 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 401 cvs_buf_write_stmp(b2, p2, NULL); 402 cvs_buf_free(b2); 403 404 diff_format = D_RCSDIFF; 405 if (cvs_diffreg(p1, p2, b3) == D_ERROR) 406 fatal("commit_diff_file: failed to get RCS patch"); 407 408 cvs_buf_putc(b3, '\0'); 409 delta = cvs_buf_release(b3); 410 return (delta); 411 } 412 413 static void 414 commit_desc_set(struct cvs_file *cf) 415 { 416 BUF *bp; 417 int l, fd; 418 char *desc_path, *desc; 419 420 desc_path = xmalloc(MAXPATHLEN); 421 l = snprintf(desc_path, MAXPATHLEN, "%s/%s%s", 422 CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT); 423 if (l == -1 || l >= MAXPATHLEN) 424 fatal("commit_desc_set: overflow"); 425 426 if ((fd = open(desc_path, O_RDONLY)) == -1) { 427 xfree(desc_path); 428 return; 429 } 430 431 bp = cvs_buf_load_fd(fd, BUF_AUTOEXT); 432 cvs_buf_putc(bp, '\0'); 433 desc = cvs_buf_release(bp); 434 435 rcs_desc_set(cf->file_rcs, desc); 436 437 (void)close(fd); 438 (void)cvs_unlink(desc_path); 439 440 xfree(desc); 441 xfree(desc_path); 442 } 443