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