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