1 /* $OpenBSD: commit.c,v 1.74 2006/06/16 14:07:42 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 "includes.h" 20 21 #include "cvs.h" 22 #include "diff.h" 23 #include "log.h" 24 25 int cvs_commit(int, char **); 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 TAILQ_INIT(&files_affected); 91 conflicts_found = 0; 92 93 cr.enterdir = NULL; 94 cr.leavedir = NULL; 95 cr.local = cvs_commit_check_conflicts; 96 cr.remote = NULL; 97 cr.flags = flags; 98 99 if (argc > 0) 100 cvs_file_run(argc, argv, &cr); 101 else 102 cvs_file_run(1, &arg, &cr); 103 104 if (conflicts_found != 0) 105 fatal("%d conflicts found, please correct these first", 106 conflicts_found); 107 108 cr.local = cvs_commit_local; 109 cvs_file_walklist(&files_affected, &cr); 110 cvs_file_freelist(&files_affected); 111 112 return (0); 113 } 114 115 void 116 cvs_commit_check_conflicts(struct cvs_file *cf) 117 { 118 cvs_log(LP_TRACE, "cvs_commit_check_conflicts(%s)", cf->file_path); 119 120 /* 121 * cvs_file_classify makes the noise for us 122 * XXX - we want that? 123 */ 124 cvs_file_classify(cf, NULL, 1); 125 126 if (cf->file_type == CVS_DIR) { 127 if (verbosity > 1) 128 cvs_log(LP_NOTICE, "Examining %s", cf->file_path); 129 return; 130 } 131 132 if (cf->file_status == FILE_CONFLICT || 133 cf->file_status == FILE_LOST || 134 cf->file_status == FILE_UNLINK) 135 conflicts_found++; 136 137 if (cf->file_status != FILE_REMOVED && 138 update_has_conflict_markers(cf)) { 139 cvs_log(LP_ERR, "conflict: unresolved conflicts in %s from " 140 "merging, please fix these first", cf->file_path); 141 conflicts_found++; 142 } 143 144 if (cf->file_status == FILE_MERGE || 145 cf->file_status == FILE_PATCH || 146 cf->file_status == FILE_CHECKOUT) { 147 cvs_log(LP_ERR, "conflict: %s is not up-to-date", 148 cf->file_path); 149 conflicts_found++; 150 } 151 152 if (cf->file_status == FILE_ADDED || 153 cf->file_status == FILE_REMOVED || 154 cf->file_status == FILE_MODIFIED) 155 cvs_file_get(cf->file_path, &files_affected); 156 } 157 158 void 159 cvs_commit_local(struct cvs_file *cf) 160 { 161 BUF *b; 162 int isnew; 163 int l, openflags, rcsflags; 164 char *d, *f, rbuf[24]; 165 CVSENTRIES *entlist; 166 char *attic, *repo, *rcsfile; 167 168 cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path); 169 cvs_file_classify(cf, NULL, 0); 170 171 if (cvs_noexec == 1) 172 return; 173 174 if (cf->file_type != CVS_FILE) 175 fatal("cvs_commit_local: '%s' is not a file", cf->file_path); 176 177 if (cf->file_status == FILE_MODIFIED || 178 cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED 179 && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1)) 180 rcsnum_tostr(rcs_head_get(cf->file_rcs), rbuf, sizeof(rbuf)); 181 else 182 strlcpy(rbuf, "Non-existent", sizeof(rbuf)); 183 184 isnew = 0; 185 if (cf->file_status == FILE_ADDED) { 186 isnew = 1; 187 rcsflags = RCS_CREATE; 188 openflags = O_CREAT | O_TRUNC | O_WRONLY; 189 if (cf->file_rcs != NULL) { 190 if (cf->file_rcs->rf_inattic == 0) 191 cvs_log(LP_ERR, "warning: expected %s " 192 "to be in the Attic", cf->file_path); 193 194 if (cf->file_rcs->rf_dead == 0) 195 cvs_log(LP_ERR, "warning: expected %s " 196 "to be dead", cf->file_path); 197 198 rcsfile = xmalloc(MAXPATHLEN); 199 repo = xmalloc(MAXPATHLEN); 200 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 201 l = snprintf(rcsfile, MAXPATHLEN, "%s/%s%s", 202 repo, cf->file_name, RCS_FILE_EXT); 203 if (l == -1 || l >= MAXPATHLEN) 204 fatal("cvs_commit_local: overflow"); 205 206 if (rename(cf->file_rpath, rcsfile) == -1) 207 fatal("cvs_commit_local: failed to move %s " 208 "outside the Attic: %s", cf->file_path, 209 strerror(errno)); 210 211 xfree(cf->file_rpath); 212 cf->file_rpath = xstrdup(rcsfile); 213 xfree(rcsfile); 214 xfree(repo); 215 216 rcsflags = RCS_READ | RCS_PARSE_FULLY; 217 openflags = O_RDONLY; 218 rcs_close(cf->file_rcs); 219 isnew = 0; 220 } 221 222 cf->repo_fd = open(cf->file_rpath, openflags); 223 if (cf->repo_fd < 0) 224 fatal("cvs_commit_local: %s", strerror(errno)); 225 226 cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd, 227 rcsflags, 0600); 228 if (cf->file_rcs == NULL) 229 fatal("cvs_commit_local: failed to create RCS file " 230 "for %s", cf->file_path); 231 232 commit_desc_set(cf); 233 } 234 235 cvs_printf("Checking in %s:\n", cf->file_path); 236 cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path); 237 cvs_printf("old revision: %s; ", rbuf); 238 239 if (isnew == 0) 240 d = commit_diff_file(cf); 241 242 if (cf->file_status == FILE_REMOVED) { 243 b = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head); 244 if (b == NULL) 245 fatal("cvs_commit_local: failed to get HEAD"); 246 } else { 247 if ((b = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL) 248 fatal("cvs_commit_local: failed to load file"); 249 } 250 251 cvs_buf_putc(b, '\0'); 252 f = cvs_buf_release(b); 253 254 if (isnew == 0) { 255 if (rcs_deltatext_set(cf->file_rcs, 256 cf->file_rcs->rf_head, d) == -1) 257 fatal("cvs_commit_local: failed to set delta"); 258 } 259 260 if (rcs_rev_add(cf->file_rcs, RCS_HEAD_REV, logmsg, -1, NULL) == -1) 261 fatal("cvs_commit_local: failed to add new revision"); 262 263 if (rcs_deltatext_set(cf->file_rcs, cf->file_rcs->rf_head, f) == -1) 264 fatal("cvs_commit_local: failed to set new HEAD delta"); 265 266 xfree(f); 267 268 if (isnew == 0) 269 xfree(d); 270 271 if (cf->file_status == FILE_REMOVED) { 272 if (rcs_state_set(cf->file_rcs, 273 cf->file_rcs->rf_head, RCS_STATE_DEAD) == -1) 274 fatal("cvs_commit_local: failed to set state"); 275 } 276 277 if (cf->file_rcs->rf_branch != NULL) { 278 rcsnum_free(cf->file_rcs->rf_branch); 279 cf->file_rcs->rf_branch = NULL; 280 } 281 282 rcs_write(cf->file_rcs); 283 284 if (cf->file_status == FILE_REMOVED) { 285 strlcpy(rbuf, "Removed", sizeof(rbuf)); 286 } else if (cf->file_status == FILE_ADDED) { 287 if (cf->file_rcs->rf_dead == 1) 288 strlcpy(rbuf, "Initial Revision", sizeof(rbuf)); 289 else 290 rcsnum_tostr(cf->file_rcs->rf_head, 291 rbuf, sizeof(rbuf)); 292 } else if (cf->file_status == FILE_MODIFIED) { 293 rcsnum_tostr(cf->file_rcs->rf_head, rbuf, sizeof(rbuf)); 294 } 295 296 cvs_printf("new revision: %s\n", rbuf); 297 298 (void)unlink(cf->file_path); 299 (void)close(cf->fd); 300 cf->fd = -1; 301 302 if (cf->file_status != FILE_REMOVED) { 303 b = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head); 304 if (b == NULL) 305 fatal("cvs_commit_local: failed to get HEAD"); 306 307 cvs_checkout_file(cf, cf->file_rcs->rf_head, b, 0); 308 } else { 309 entlist = cvs_ent_open(cf->file_wd); 310 cvs_ent_remove(entlist, cf->file_name); 311 cvs_ent_close(entlist, ENT_SYNC); 312 313 repo = xmalloc(MAXPATHLEN); 314 attic = xmalloc(MAXPATHLEN); 315 cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); 316 317 l = snprintf(attic, MAXPATHLEN, "%s/%s", repo, CVS_PATH_ATTIC); 318 if (l == -1 || l >= MAXPATHLEN) 319 fatal("cvs_commit_local: overflow"); 320 321 if (mkdir(attic, 0755) == -1 && errno != EEXIST) 322 fatal("cvs_commit_local: failed to create Attic"); 323 324 l = snprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo, 325 CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT); 326 if (l == -1 || l >= MAXPATHLEN) 327 fatal("cvs_commit_local: overflow"); 328 329 if (rename(cf->file_rpath, attic) == -1) 330 fatal("cvs_commit_local: failed to move %s to Attic", 331 cf->file_path); 332 333 xfree(repo); 334 xfree(attic); 335 } 336 337 cvs_printf("done\n"); 338 339 } 340 341 static char * 342 commit_diff_file(struct cvs_file *cf) 343 { 344 char*delta, *p1, *p2; 345 BUF *b1, *b2, *b3; 346 347 if (cf->file_status == FILE_MODIFIED || 348 cf->file_status == FILE_ADDED) { 349 if ((b1 = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL) 350 fatal("commit_diff_file: failed to load '%s'", 351 cf->file_path); 352 } else { 353 b1 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head); 354 if (b1 == NULL) 355 fatal("commit_diff_file: failed to load HEAD"); 356 b1 = rcs_kwexp_buf(b1, cf->file_rcs, cf->file_rcs->rf_head); 357 } 358 359 if ((b2 = rcs_getrev(cf->file_rcs, cf->file_rcs->rf_head)) == NULL) 360 fatal("commit_diff_file: failed to load HEAD for '%s'", 361 cf->file_path); 362 363 if ((b3 = cvs_buf_alloc(128, BUF_AUTOEXT)) == NULL) 364 fatal("commit_diff_file: failed to create diff buf"); 365 366 (void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir); 367 cvs_buf_write_stmp(b1, p1, 0600, NULL); 368 cvs_buf_free(b1); 369 370 (void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir); 371 cvs_buf_write_stmp(b2, p2, 0600, NULL); 372 cvs_buf_free(b2); 373 374 diff_format = D_RCSDIFF; 375 if (cvs_diffreg(p1, p2, b3) == D_ERROR) 376 fatal("commit_diff_file: failed to get RCS patch"); 377 378 cvs_buf_putc(b3, '\0'); 379 delta = cvs_buf_release(b3); 380 return (delta); 381 } 382 383 static void 384 commit_desc_set(struct cvs_file *cf) 385 { 386 BUF *bp; 387 int l; 388 char *desc_path, *desc; 389 390 desc_path = xmalloc(MAXPATHLEN); 391 l = snprintf(desc_path, MAXPATHLEN, "%s/%s%s", 392 CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT); 393 if (l == -1 || l >= MAXPATHLEN) 394 fatal("commit_desc_set: overflow"); 395 396 if ((bp = cvs_buf_load(desc_path, BUF_AUTOEXT)) == NULL) { 397 xfree(desc_path); 398 return; 399 } 400 401 cvs_buf_putc(bp, '\0'); 402 desc = cvs_buf_release(bp); 403 404 rcs_desc_set(cf->file_rcs, desc); 405 406 (void)cvs_unlink(desc_path); 407 408 xfree(desc); 409 xfree(desc_path); 410 } 411