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