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