xref: /openbsd-src/usr.bin/cvs/commit.c (revision f331ff59831f242d2460f823c6dba145a2176394)
1 /*	$OpenBSD: commit.c,v 1.124 2008/01/31 10:15:05 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 			}
222 
223 			brev = rcs_translate_tag(tag, cf->file_rcs);
224 
225 			if (brev == NULL) {
226 				fatal("failed to resolve tag: %s",
227 				    cf->file_ent->ce_tag);
228 			}
229 
230 			rcsnum_tostr(brev, rev, sizeof(rev));
231 			if ((branch = rcsnum_revtobr(brev)) == NULL) {
232 				cvs_log(LP_ERR, "%s is not a branch revision",
233 				    rev);
234 				conflicts_found++;
235 				rcsnum_free(brev);
236 				return;
237 			}
238 
239 			if (!RCSNUM_ISBRANCHREV(brev)) {
240 				cvs_log(LP_ERR, "%s is not a branch revision",
241 				    rev);
242 				conflicts_found++;
243 				rcsnum_free(branch);
244 				rcsnum_free(brev);
245 				return;
246 			}
247 
248 			rcsnum_tostr(branch, rev, sizeof(rev));
249 			if (!RCSNUM_ISBRANCH(branch)) {
250 				cvs_log(LP_ERR, "%s (%s) is not a branch",
251 				    cf->file_ent->ce_tag, rev);
252 				conflicts_found++;
253 				rcsnum_free(branch);
254 				rcsnum_free(brev);
255 				return;
256 			}
257 		}
258 	}
259 
260 next:
261 	if (branch != NULL)
262 		rcsnum_free(branch);
263 	if (brev != NULL)
264 		rcsnum_free(brev);
265 
266 	if (cf->file_status == FILE_ADDED ||
267 	    cf->file_status == FILE_REMOVED ||
268 	    cf->file_status == FILE_MODIFIED)
269 		cvs_file_get(cf->file_path, 0, &files_affected);
270 
271 	switch (cf->file_status) {
272 	case FILE_ADDED:
273 		cvs_file_get(cf->file_path, 0, &files_added);
274 		break;
275 	case FILE_REMOVED:
276 		cvs_file_get(cf->file_path, 0, &files_removed);
277 		break;
278 	case FILE_MODIFIED:
279 		cvs_file_get(cf->file_path, 0, &files_modified);
280 		break;
281 	}
282 }
283 
284 void
285 cvs_commit_local(struct cvs_file *cf)
286 {
287 	char *tag;
288 	BUF *b, *d;
289 	int onbranch, isnew, histtype;
290 	RCSNUM *nrev, *crev, *rrev, *brev;
291 	int openflags, rcsflags;
292 	char rbuf[CVS_REV_BUFSZ], nbuf[CVS_REV_BUFSZ];
293 	CVSENTRIES *entlist;
294 	char attic[MAXPATHLEN], repo[MAXPATHLEN], rcsfile[MAXPATHLEN];
295 
296 	cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path);
297 	cvs_file_classify(cf, cvs_directory_tag);
298 
299 	if (cvs_noexec == 1)
300 		return;
301 
302 	if (cf->file_type != CVS_FILE)
303 		fatal("cvs_commit_local: '%s' is not a file", cf->file_path);
304 
305 	if (cf->file_status != FILE_MODIFIED &&
306 	    cf->file_status != FILE_ADDED &&
307 	    cf->file_status != FILE_REMOVED) {
308 		cvs_log(LP_ERR, "skipping bogus file `%s'", cf->file_path);
309 		return;
310 	}
311 
312 	onbranch = 0;
313 	nrev = RCS_HEAD_REV;
314 	crev = NULL;
315 	rrev = NULL;
316 
317 	if (cf->file_rcs != NULL && cf->file_rcs->rf_branch != NULL) {
318 		rcsnum_free(cf->file_rcs->rf_branch);
319 		cf->file_rcs->rf_branch = NULL;
320 	}
321 
322 	if (cf->file_status == FILE_MODIFIED ||
323 	    cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED
324 	    && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1)) {
325 		rrev = rcs_head_get(cf->file_rcs);
326 		crev = rcs_head_get(cf->file_rcs);
327 		if (crev == NULL || rrev == NULL)
328 			fatal("RCS head empty or missing in %s\n",
329 			    cf->file_rcs->rf_path);
330 
331 		tag = cvs_directory_tag;
332 		if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL)
333 			tag = cf->file_ent->ce_tag;
334 
335 		if (tag != NULL) {
336 			rcsnum_free(crev);
337 			crev = rcs_translate_tag(tag, cf->file_rcs);
338 			if (crev == NULL) {
339 				fatal("failed to resolve existing tag: %s",
340 				    tag);
341 			}
342 
343 			if (RCSNUM_ISBRANCHREV(crev)) {
344 				nrev = rcsnum_alloc();
345 				rcsnum_cpy(crev, nrev, 0);
346 				rcsnum_inc(nrev);
347 			} else if (!RCSNUM_ISBRANCH(crev)) {
348 				brev = rcs_sym_getrev(cf->file_rcs, tag);
349 				if (brev == NULL)
350 					fatal("no more tag?");
351 				nrev = rcsnum_brtorev(brev);
352 				if (nrev == NULL)
353 					fatal("failed to create branch rev");
354 				rcsnum_free(brev);
355 			} else {
356 				fatal("this isnt suppose to happen, honestly");
357 			}
358 
359 			rcsnum_free(rrev);
360 			rrev = rcsnum_branch_root(nrev);
361 
362 			/* branch stuff was checked in cvs_commit_check_files */
363 			onbranch = 1;
364 		}
365 
366 		rcsnum_tostr(crev, rbuf, sizeof(rbuf));
367 	} else {
368 		strlcpy(rbuf, "Non-existent", sizeof(rbuf));
369 	}
370 
371 	if (rrev != NULL)
372 		rcsnum_free(rrev);
373 	isnew = 0;
374 	if (cf->file_status == FILE_ADDED) {
375 		isnew = 1;
376 		rcsflags = RCS_CREATE;
377 		openflags = O_CREAT | O_TRUNC | O_WRONLY;
378 		if (cf->file_rcs != NULL) {
379 			if (cf->in_attic == 0)
380 				cvs_log(LP_ERR, "warning: expected %s "
381 				    "to be in the Attic", cf->file_path);
382 
383 			if (cf->file_rcs->rf_dead == 0)
384 				cvs_log(LP_ERR, "warning: expected %s "
385 				    "to be dead", cf->file_path);
386 
387 			cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
388 			(void)xsnprintf(rcsfile, MAXPATHLEN, "%s/%s%s",
389 			    repo, cf->file_name, RCS_FILE_EXT);
390 
391 			if (rename(cf->file_rpath, rcsfile) == -1)
392 				fatal("cvs_commit_local: failed to move %s "
393 				    "outside the Attic: %s", cf->file_path,
394 				    strerror(errno));
395 
396 			xfree(cf->file_rpath);
397 			cf->file_rpath = xstrdup(rcsfile);
398 
399 			rcsflags = RCS_READ | RCS_PARSE_FULLY;
400 			openflags = O_RDONLY;
401 			rcs_close(cf->file_rcs);
402 			isnew = 0;
403 		}
404 
405 		cf->repo_fd = open(cf->file_rpath, openflags);
406 		if (cf->repo_fd < 0)
407 			fatal("cvs_commit_local: %s", strerror(errno));
408 
409 		cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd,
410 		    rcsflags, 0444);
411 		if (cf->file_rcs == NULL)
412 			fatal("cvs_commit_local: failed to create RCS file "
413 			    "for %s", cf->file_path);
414 
415 		commit_desc_set(cf);
416 	}
417 
418 	if (verbosity > 1) {
419 		cvs_printf("Checking in %s:\n", cf->file_path);
420 		cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path);
421 		cvs_printf("old revision: %s; ", rbuf);
422 	}
423 
424 	if (isnew == 0 && onbranch == 0)
425 		d = commit_diff(cf, cf->file_rcs->rf_head, 0);
426 
427 	if (cf->file_status == FILE_REMOVED) {
428 		b = rcs_rev_getbuf(cf->file_rcs, crev, 0);
429 		if (b == NULL)
430 			fatal("cvs_commit_local: failed to get crev");
431 	} else if (onbranch == 1) {
432 		b = commit_diff(cf, crev, 1);
433 	} else {
434 		if ((b = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL)
435 			fatal("cvs_commit_local: failed to load file");
436 	}
437 
438 	if (isnew == 0 && onbranch == 0) {
439 		if (rcs_deltatext_set(cf->file_rcs, crev, d) == -1)
440 			fatal("cvs_commit_local: failed to set delta");
441 	}
442 
443 	if (rcs_rev_add(cf->file_rcs, nrev, logmsg, -1, NULL) == -1)
444 		fatal("cvs_commit_local: failed to add new revision");
445 
446 	if (nrev == RCS_HEAD_REV)
447 		nrev = cf->file_rcs->rf_head;
448 
449 	if (rcs_deltatext_set(cf->file_rcs, nrev, b) == -1)
450 		fatal("cvs_commit_local: failed to set new HEAD delta");
451 
452 	if (cf->file_status == FILE_REMOVED) {
453 		if (rcs_state_set(cf->file_rcs, nrev, RCS_STATE_DEAD) == -1)
454 			fatal("cvs_commit_local: failed to set state");
455 	}
456 
457 	if (cf->file_status == FILE_ADDED && cf->file_ent->ce_opts != NULL) {
458 		int kflag;
459 
460 		kflag = rcs_kflag_get(cf->file_ent->ce_opts + 2);
461 		rcs_kwexp_set(cf->file_rcs, kflag);
462 	}
463 
464 	rcs_write(cf->file_rcs);
465 
466 	if (cf->file_status == FILE_REMOVED) {
467 		strlcpy(nbuf, "Removed", sizeof(nbuf));
468 	} else if (cf->file_status == FILE_ADDED) {
469 		if (cf->file_rcs->rf_dead == 1)
470 			strlcpy(nbuf, "Initial Revision", sizeof(nbuf));
471 		else
472 			rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
473 	} else if (cf->file_status == FILE_MODIFIED) {
474 		rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
475 	}
476 
477 	if (verbosity > 1)
478 		cvs_printf("new revision: %s\n", nbuf);
479 
480 	(void)unlink(cf->file_path);
481 	(void)close(cf->fd);
482 	cf->fd = -1;
483 
484 	if (cf->file_status != FILE_REMOVED) {
485 		cvs_checkout_file(cf, nrev, NULL, CO_COMMIT);
486 	} else {
487 		entlist = cvs_ent_open(cf->file_wd);
488 		cvs_ent_remove(entlist, cf->file_name);
489 		cvs_ent_close(entlist, ENT_SYNC);
490 
491 		cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
492 
493 		(void)xsnprintf(attic, MAXPATHLEN, "%s/%s",
494 		    repo, CVS_PATH_ATTIC);
495 
496 		if (mkdir(attic, 0755) == -1 && errno != EEXIST)
497 			fatal("cvs_commit_local: failed to create Attic");
498 
499 		(void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo,
500 		    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
501 
502 		if (rename(cf->file_rpath, attic) == -1)
503 			fatal("cvs_commit_local: failed to move %s to Attic",
504 			    cf->file_path);
505 
506 		if (cvs_server_active == 1)
507 			cvs_server_update_entry("Remove-entry", cf);
508 	}
509 
510 	if (verbosity > 1)
511 		cvs_printf("done\n");
512 	else {
513 		cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s",
514 		    cf->file_path, rbuf, nbuf);
515 	}
516 
517 	switch (cf->file_status) {
518 	case FILE_MODIFIED:
519 		histtype = CVS_HISTORY_COMMIT_MODIFIED;
520 		break;
521 	case FILE_ADDED:
522 		histtype = CVS_HISTORY_COMMIT_ADDED;
523 		break;
524 	case FILE_REMOVED:
525 		histtype = CVS_HISTORY_COMMIT_REMOVED;
526 		break;
527 	}
528 
529 	cvs_history_add(histtype, cf, NULL);
530 }
531 
532 static BUF *
533 commit_diff(struct cvs_file *cf, RCSNUM *rev, int reverse)
534 {
535 	char *p1, *p2, *p;
536 	BUF *b;
537 
538 	(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
539 
540 	if (cf->file_status == FILE_MODIFIED ||
541 	    cf->file_status == FILE_ADDED) {
542 		if ((b = cvs_buf_load_fd(cf->fd, BUF_AUTOEXT)) == NULL)
543 			fatal("commit_diff: failed to load '%s'",
544 			    cf->file_path);
545 		cvs_buf_write_stmp(b, p1, NULL);
546 		cvs_buf_free(b);
547 	} else {
548 		rcs_rev_write_stmp(cf->file_rcs, rev, p1, 0);
549 	}
550 
551 	(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
552 	rcs_rev_write_stmp(cf->file_rcs, rev, p2, RCS_KWEXP_NONE);
553 
554 	if ((b = cvs_buf_alloc(128, BUF_AUTOEXT)) == NULL)
555 		fatal("commit_diff: failed to create diff buf");
556 
557 	diff_format = D_RCSDIFF;
558 
559 	if (reverse == 1) {
560 		p = p1;
561 		p1 = p2;
562 		p2 = p;
563 	}
564 
565 	if (cvs_diffreg(p1, p2, b) == D_ERROR)
566 		fatal("commit_diff: failed to get RCS patch");
567 
568 	xfree(p1);
569 	xfree(p2);
570 
571 	return (b);
572 }
573 
574 static void
575 commit_desc_set(struct cvs_file *cf)
576 {
577 	BUF *bp;
578 	int fd;
579 	char desc_path[MAXPATHLEN], *desc;
580 
581 	(void)xsnprintf(desc_path, MAXPATHLEN, "%s/%s%s",
582 	    CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT);
583 
584 	if ((fd = open(desc_path, O_RDONLY)) == -1)
585 		return;
586 
587 	bp = cvs_buf_load_fd(fd, BUF_AUTOEXT);
588 	cvs_buf_putc(bp, '\0');
589 	desc = cvs_buf_release(bp);
590 
591 	rcs_desc_set(cf->file_rcs, desc);
592 
593 	(void)close(fd);
594 	(void)cvs_unlink(desc_path);
595 
596 	xfree(desc);
597 }
598