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