xref: /openbsd-src/usr.bin/cvs/commit.c (revision b034d592535c9013a730be7c6d5fe362a29dde95)
1 /*	$OpenBSD: commit.c,v 1.137 2008/06/10 01:00:34 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 <libgen.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "cvs.h"
28 #include "diff.h"
29 #include "remote.h"
30 
31 struct	cvs_dirlist {
32 	char	*file_path;
33 	struct	cvs_flisthead files_affected;
34 	struct	cvs_flisthead files_added;
35 	struct	cvs_flisthead files_removed;
36 	struct	cvs_flisthead files_modified;
37 	TAILQ_ENTRY(cvs_dirlist) dlist;
38 };
39 
40 TAILQ_HEAD(cvs_dlisthead, cvs_dirlist);
41 
42 void			 cvs_commit_local(struct cvs_file *);
43 void			 cvs_commit_check_files(struct cvs_file *);
44 void			 cvs_commit_loginfo(char *, struct cvs_dirlist *);
45 void			 cvs_commit_lock_dirs(struct cvs_file *);
46 struct cvs_dirlist	*cvs_commit_getdir(char *dir);
47 void			 cvs_commit_freedirlist(void);
48 
49 static BUF *commit_diff(struct cvs_file *, RCSNUM *, int);
50 static void commit_desc_set(struct cvs_file *);
51 
52 struct	cvs_dlisthead	 directory_list;
53 struct	file_info_list	 files_info;
54 struct	trigger_list	*line_list;
55 
56 int	conflicts_found;
57 char	*logmsg = NULL;
58 char	*loginfo = NULL;
59 
60 struct cvs_cmd cvs_cmd_commit = {
61 	CVS_OP_COMMIT, CVS_USE_WDIR | CVS_LOCK_REPO, "commit",
62 	{ "ci", "com" },
63 	"Check files into the repository",
64 	"[-flR] [-F logfile | -m msg] [-r rev] ...",
65 	"F:flm:Rr:",
66 	NULL,
67 	cvs_commit
68 };
69 
70 int
71 cvs_commit(int argc, char **argv)
72 {
73 	int flags;
74 	int ch, Fflag, mflag;
75 	struct module_checkout *mc;
76 	struct cvs_recursion cr;
77 	struct cvs_dirlist *d;
78 	struct cvs_filelist *l;
79 	struct file_info *fi;
80 	char *arg = ".", repo[MAXPATHLEN];
81 
82 	flags = CR_RECURSE_DIRS;
83 	Fflag = mflag = 0;
84 
85 	while ((ch = getopt(argc, argv, cvs_cmd_commit.cmd_opts)) != -1) {
86 		switch (ch) {
87 		case 'F':
88 			/* free previously assigned value */
89 			if (logmsg != NULL)
90 				xfree(logmsg);
91 			logmsg = cvs_logmsg_read(optarg);
92 			Fflag = 1;
93 			break;
94 		case 'f':
95 			break;
96 		case 'l':
97 			flags &= ~CR_RECURSE_DIRS;
98 			break;
99 		case 'm':
100 			/* free previously assigned value */
101 			if (logmsg != NULL)
102 				xfree(logmsg);
103 			logmsg = xstrdup(optarg);
104 			mflag = 1;
105 			break;
106 		case 'R':
107 			flags |= CR_RECURSE_DIRS;
108 			break;
109 		case 'r':
110 			break;
111 		default:
112 			fatal("%s", cvs_cmd_commit.cmd_synopsis);
113 		}
114 	}
115 
116 	argc -= optind;
117 	argv += optind;
118 
119 	/* -F and -m are mutually exclusive */
120 	if (Fflag && mflag)
121 		fatal("cannot specify both a log file and a message");
122 
123 	TAILQ_INIT(&directory_list);
124 	TAILQ_INIT(&files_info);
125 	conflicts_found = 0;
126 
127 	cr.enterdir = NULL;
128 	cr.leavedir = NULL;
129 	cr.fileproc = cvs_commit_check_files;
130 	cr.flags = flags;
131 
132 	if (argc > 0)
133 		cvs_file_run(argc, argv, &cr);
134 	else
135 		cvs_file_run(1, &arg, &cr);
136 
137 	if (conflicts_found != 0)
138 		fatal("%d conflicts found, please correct these first",
139 		    conflicts_found);
140 
141 	if (TAILQ_EMPTY(&directory_list))
142 		return (0);
143 
144 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
145 		d = TAILQ_FIRST(&directory_list);
146 
147 		if (logmsg == NULL) {
148 			logmsg = cvs_logmsg_create(NULL, &d->files_added,
149 			    &d->files_removed, &d->files_modified);
150 		}
151 
152 		if (logmsg == NULL)
153 			fatal("This shouldnt happen, honestly!");
154 
155 		cvs_client_connect_to_server();
156 		cr.fileproc = cvs_client_sendfile;
157 
158 		if (argc > 0)
159 			cvs_file_run(argc, argv, &cr);
160 		else
161 			cvs_file_run(1, &arg, &cr);
162 
163 		if (!(flags & CR_RECURSE_DIRS))
164 			cvs_client_send_request("Argument -l");
165 
166 		cvs_client_send_logmsg(logmsg);
167 		cvs_client_send_files(argv, argc);
168 		cvs_client_senddir(".");
169 		cvs_client_send_request("ci");
170 		cvs_client_get_responses();
171 	} else {
172 		if (cvs_server_active && logmsg == NULL)
173 			fatal("no log message specified");
174 
175 		TAILQ_FOREACH(d, &directory_list, dlist) {
176 			cvs_get_repository_name(d->file_path, repo,
177 			    MAXPATHLEN);
178 
179 			if (!Fflag && !mflag) {
180 				if (logmsg != NULL)
181 					xfree(logmsg);
182 				logmsg = cvs_logmsg_create(d->file_path,
183 				    &d->files_added, &d->files_removed,
184 				    &d->files_modified);
185 			}
186 
187 			line_list = cvs_trigger_getlines(CVS_PATH_COMMITINFO,
188 			    repo);
189 			if (line_list != NULL) {
190 				TAILQ_FOREACH(l, &d->files_affected, flist) {
191 					fi = xcalloc(1, sizeof(*fi));
192 					fi->file_path = xstrdup(l->file_path);
193 					TAILQ_INSERT_TAIL(&files_info, fi,
194 					    flist);
195 				}
196 				if (cvs_trigger_handle(CVS_TRIGGER_COMMITINFO,
197 				    repo, NULL, line_list, &files_info)) {
198 					cvs_log(LP_ERR,
199 					    "Pre-commit check failed");
200 					cvs_trigger_freelist(line_list);
201 					goto end;
202 				}
203 				cvs_trigger_freelist(line_list);
204 				cvs_trigger_freeinfo(&files_info);
205 			}
206 
207 			if (logmsg == NULL) {
208 				if (cvs_server_active)
209 					fatal("no log message specified");
210 				logmsg = cvs_logmsg_create(d->file_path,
211 				    &d->files_added, &d->files_removed,
212 				    &d->files_modified);
213 			}
214 
215 			if (cvs_logmsg_verify(logmsg))
216 				goto end;
217 
218 			cr.fileproc = cvs_commit_lock_dirs;
219 			cvs_file_walklist(&d->files_affected, &cr);
220 
221 			line_list = cvs_trigger_getlines(CVS_PATH_LOGINFO,
222 			    repo);
223 
224 			cr.fileproc = cvs_commit_local;
225 			cvs_file_walklist(&d->files_affected, &cr);
226 
227 			if (line_list != NULL) {
228 				cvs_commit_loginfo(repo, d);
229 
230 				cvs_trigger_handle(CVS_TRIGGER_LOGINFO, repo,
231 				    loginfo, line_list, &files_info);
232 
233 				xfree(loginfo);
234 				cvs_trigger_freelist(line_list);
235 				cvs_trigger_freeinfo(&files_info);
236 			}
237 
238 			mc = cvs_module_lookup(repo);
239 			if (mc->mc_prog != NULL &&
240 			    (mc->mc_flags & MODULE_RUN_ON_COMMIT))
241 				cvs_exec(mc->mc_prog, NULL, 0);
242 		}
243 	}
244 
245 end:
246 	cvs_commit_freedirlist();
247 	cvs_trigger_freeinfo(&files_info);
248 	xfree(logmsg);
249 	return (0);
250 }
251 
252 void
253 cvs_commit_freedirlist(void)
254 {
255 	struct cvs_dirlist *d;
256 
257 	while ((d = TAILQ_FIRST(&directory_list)) != NULL) {
258 		TAILQ_REMOVE(&directory_list, d, dlist);
259 		xfree(d->file_path);
260 		cvs_file_freelist(&d->files_affected);
261 		cvs_file_freelist(&d->files_added);
262 		cvs_file_freelist(&d->files_modified);
263 		cvs_file_freelist(&d->files_removed);
264 		xfree(d);
265 	}
266 }
267 
268 struct cvs_dirlist *
269 cvs_commit_getdir(char *dir)
270 {
271 	struct cvs_dirlist *dp;
272 
273 	TAILQ_FOREACH(dp, &directory_list, dlist) {
274 		if (strcmp(dp->file_path, dir) == 0)
275 			return dp;
276 	}
277 
278 	dp = xmalloc(sizeof(*dp));
279 	dp->file_path = xstrdup(dir);
280 	TAILQ_INIT(&dp->files_affected);
281 	TAILQ_INIT(&dp->files_added);
282 	TAILQ_INIT(&dp->files_modified);
283 	TAILQ_INIT(&dp->files_removed);
284 
285 	TAILQ_INSERT_TAIL(&directory_list, dp, dlist);
286 	return dp;
287 }
288 
289 void
290 cvs_commit_loginfo(char *repo, struct cvs_dirlist *d)
291 {
292 	BUF *buf;
293 	char pwd[MAXPATHLEN], *p;
294 	struct cvs_filelist *cf;
295 
296 	if (getcwd(pwd, sizeof(pwd)) == NULL)
297 		fatal("Can't get working directory");
298 
299 	buf = cvs_buf_alloc(1024);
300 
301 	cvs_trigger_loginfo_header(buf, repo);
302 
303 	if (!TAILQ_EMPTY(&d->files_added)) {
304 		cvs_buf_puts(buf, "Added Files:");
305 
306 		TAILQ_FOREACH(cf, &d->files_added, flist) {
307 			if ((p = basename(cf->file_path)) == NULL)
308 				p = cf->file_path;
309 
310 			cvs_buf_putc(buf, '\n');
311 			cvs_buf_putc(buf, '\t');
312 			cvs_buf_puts(buf, p);
313 		}
314 
315 		cvs_buf_putc(buf, '\n');
316 	}
317 
318 	if (!TAILQ_EMPTY(&d->files_modified)) {
319 		cvs_buf_puts(buf, "Modified Files:");
320 
321 		TAILQ_FOREACH(cf, &d->files_modified, flist) {
322 			if ((p = basename(cf->file_path)) == NULL)
323 				p = cf->file_path;
324 
325 			cvs_buf_putc(buf, '\n');
326 			cvs_buf_putc(buf, '\t');
327 			cvs_buf_puts(buf, p);
328 		}
329 
330 		cvs_buf_putc(buf, '\n');
331 	}
332 
333 	if (!TAILQ_EMPTY(&d->files_removed)) {
334 		cvs_buf_puts(buf, "Removed Files:");
335 
336 		TAILQ_FOREACH(cf, &d->files_removed, flist) {
337 			if ((p = basename(cf->file_path)) == NULL)
338 				p = cf->file_path;
339 
340 			cvs_buf_putc(buf, '\n');
341 			cvs_buf_putc(buf, '\t');
342 			cvs_buf_puts(buf, p);
343 		}
344 
345 		cvs_buf_putc(buf, '\n');
346 	}
347 
348 	cvs_buf_puts(buf, "Log Message:\n");
349 
350 	cvs_buf_puts(buf, logmsg);
351 
352 	cvs_buf_putc(buf, '\n');
353 	cvs_buf_putc(buf, '\0');
354 
355 	loginfo = cvs_buf_release(buf);
356 }
357 
358 void
359 cvs_commit_lock_dirs(struct cvs_file *cf)
360 {
361 	char repo[MAXPATHLEN];
362 
363 	cvs_get_repository_path(cf->file_wd, repo, sizeof(repo));
364 	cvs_log(LP_TRACE, "cvs_commit_lock_dirs: %s", repo);
365 
366 	/* locks stay in place until we are fully done and exit */
367 	cvs_repository_lock(repo, 1);
368 }
369 
370 void
371 cvs_commit_check_files(struct cvs_file *cf)
372 {
373 	char *tag;
374 	RCSNUM *branch, *brev;
375 	struct cvs_dirlist *d;
376 
377 	branch = brev = NULL;
378 
379 	cvs_log(LP_TRACE, "cvs_commit_check_files(%s)", cf->file_path);
380 
381 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL)
382 		cvs_remote_classify_file(cf);
383 	else
384 		cvs_file_classify(cf, cvs_directory_tag);
385 
386 	if (cf->file_type == CVS_DIR) {
387 		if (verbosity > 1)
388 			cvs_log(LP_NOTICE, "Examining %s", cf->file_path);
389 		return;
390 	}
391 
392 	if (cf->file_status == FILE_UPTODATE)
393 		return;
394 
395 	if (cf->file_status == FILE_MERGE ||
396 	    cf->file_status == FILE_PATCH ||
397 	    cf->file_status == FILE_CHECKOUT ||
398 	    cf->file_status == FILE_LOST ||
399 	    cf->file_status == FILE_UNLINK) {
400 		cvs_log(LP_ERR, "conflict: %s is not up-to-date",
401 		    cf->file_path);
402 		conflicts_found++;
403 		return;
404 	}
405 
406 	if (cf->file_status == FILE_CONFLICT &&
407 	   cf->file_ent->ce_conflict != NULL) {
408 		cvs_log(LP_ERR, "conflict: unresolved conflicts in %s from "
409 		    "merging, please fix these first", cf->file_path);
410 		conflicts_found++;
411 		return;
412 	}
413 
414 	if (cf->file_status == FILE_MODIFIED &&
415 	    cf->file_ent->ce_conflict != NULL &&
416 	    update_has_conflict_markers(cf)) {
417 		cvs_log(LP_ERR, "warning: file %s seems to still contain "
418 		    "conflict indicators", cf->file_path);
419 	}
420 
421 	if (cf->file_ent != NULL && cf->file_ent->ce_date != -1) {
422 		cvs_log(LP_ERR, "conflict: cannot commit to sticky date for %s",
423 		    cf->file_path);
424 		conflicts_found++;
425 		return;
426 	}
427 
428 	if (current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
429 		tag = cvs_directory_tag;
430 		if (cf->file_ent != NULL)
431 			tag = cf->file_ent->ce_tag;
432 
433 		if (tag != NULL && cf->file_rcs != NULL) {
434 			brev = rcs_sym_getrev(cf->file_rcs, tag);
435 			if (brev != NULL) {
436 				if (RCSNUM_ISBRANCH(brev))
437 					goto next;
438 				rcsnum_free(brev);
439 			}
440 
441 			brev = rcs_translate_tag(tag, cf->file_rcs);
442 
443 			if (brev == NULL) {
444 				fatal("failed to resolve tag: %s",
445 				    cf->file_ent->ce_tag);
446 			}
447 
448 			if ((branch = rcsnum_revtobr(brev)) == NULL) {
449 				cvs_log(LP_ERR, "sticky tag %s is not "
450 				    "a branch for file %s", tag,
451 				    cf->file_path);
452 				conflicts_found++;
453 				rcsnum_free(brev);
454 				return;
455 			}
456 
457 			if (!RCSNUM_ISBRANCHREV(brev)) {
458 				cvs_log(LP_ERR, "sticky tag %s is not "
459 				    "a branch for file %s", tag,
460 				    cf->file_path);
461 				conflicts_found++;
462 				rcsnum_free(branch);
463 				rcsnum_free(brev);
464 				return;
465 			}
466 
467 			if (!RCSNUM_ISBRANCH(branch)) {
468 				cvs_log(LP_ERR, "sticky tag %s is not "
469 				    "a branch for file %s", tag,
470 				    cf->file_path);
471 				conflicts_found++;
472 				rcsnum_free(branch);
473 				rcsnum_free(brev);
474 				return;
475 			}
476 		}
477 	}
478 
479 next:
480 	if (branch != NULL)
481 		rcsnum_free(branch);
482 	if (brev != NULL)
483 		rcsnum_free(brev);
484 
485 	if (cf->file_status != FILE_ADDED &&
486 	    cf->file_status != FILE_REMOVED &&
487 	    cf->file_status != FILE_MODIFIED)
488 		return;
489 
490 	if (current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
491 		d = cvs_commit_getdir(cf->file_wd);
492 	} else {
493 		d = cvs_commit_getdir("remote");
494 	}
495 
496 	cvs_file_get(cf->file_path, 0, &d->files_affected);
497 
498 	switch (cf->file_status) {
499 	case FILE_ADDED:
500 		cvs_file_get(cf->file_path, 0, &d->files_added);
501 		break;
502 	case FILE_REMOVED:
503 		cvs_file_get(cf->file_path, 0, &d->files_removed);
504 		break;
505 	case FILE_MODIFIED:
506 		cvs_file_get(cf->file_path, 0, &d->files_modified);
507 		break;
508 	}
509 }
510 
511 void
512 cvs_commit_local(struct cvs_file *cf)
513 {
514 	char *tag;
515 	BUF *b, *d;
516 	int onbranch, isnew, histtype;
517 	RCSNUM *nrev, *crev, *rrev, *brev;
518 	int openflags, rcsflags;
519 	char rbuf[CVS_REV_BUFSZ], nbuf[CVS_REV_BUFSZ];
520 	CVSENTRIES *entlist;
521 	char attic[MAXPATHLEN], repo[MAXPATHLEN], rcsfile[MAXPATHLEN];
522 	struct file_info *fi;
523 
524 	cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path);
525 	cvs_file_classify(cf, cvs_directory_tag);
526 
527 	if (cvs_noexec == 1)
528 		return;
529 
530 	if (cf->file_type != CVS_FILE)
531 		fatal("cvs_commit_local: '%s' is not a file", cf->file_path);
532 
533 	if (cf->file_status != FILE_MODIFIED &&
534 	    cf->file_status != FILE_ADDED &&
535 	    cf->file_status != FILE_REMOVED) {
536 		cvs_log(LP_ERR, "skipping bogus file `%s'", cf->file_path);
537 		return;
538 	}
539 
540 	onbranch = 0;
541 	nrev = RCS_HEAD_REV;
542 	crev = NULL;
543 	rrev = NULL;
544 
545 	if (cf->file_rcs != NULL && cf->file_rcs->rf_branch != NULL) {
546 		rcsnum_free(cf->file_rcs->rf_branch);
547 		cf->file_rcs->rf_branch = NULL;
548 	}
549 
550 	if (cf->file_status == FILE_MODIFIED ||
551 	    cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED
552 	    && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1)) {
553 		rrev = rcs_head_get(cf->file_rcs);
554 		crev = rcs_head_get(cf->file_rcs);
555 		if (crev == NULL || rrev == NULL)
556 			fatal("RCS head empty or missing in %s\n",
557 			    cf->file_rcs->rf_path);
558 
559 		tag = cvs_directory_tag;
560 		if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL)
561 			tag = cf->file_ent->ce_tag;
562 
563 		if (tag != NULL) {
564 			rcsnum_free(crev);
565 			rcsnum_free(rrev);
566 			brev = rcs_sym_getrev(cf->file_rcs, tag);
567 			crev = rcs_translate_tag(tag, cf->file_rcs);
568 			if (brev == NULL || crev == NULL) {
569 				fatal("failed to resolve existing tag: %s",
570 				    tag);
571 			}
572 
573 			rrev = rcsnum_alloc();
574 			rcsnum_cpy(brev, rrev, brev->rn_len - 1);
575 
576 			if (RCSNUM_ISBRANCHREV(crev) &&
577 			    rcsnum_cmp(crev, rrev, 0)) {
578 				nrev = rcsnum_alloc();
579 				rcsnum_cpy(crev, nrev, 0);
580 				rcsnum_inc(nrev);
581 			} else if (!RCSNUM_ISBRANCH(crev)) {
582 				nrev = rcsnum_brtorev(brev);
583 				if (nrev == NULL)
584 					fatal("failed to create branch rev");
585 			} else {
586 				fatal("this isnt suppose to happen, honestly");
587 			}
588 
589 			rcsnum_free(brev);
590 			rcsnum_free(rrev);
591 			rrev = rcsnum_branch_root(nrev);
592 
593 			/* branch stuff was checked in cvs_commit_check_files */
594 			onbranch = 1;
595 		}
596 
597 		rcsnum_tostr(crev, rbuf, sizeof(rbuf));
598 	} else {
599 		strlcpy(rbuf, "Non-existent", sizeof(rbuf));
600 	}
601 
602 	if (rrev != NULL)
603 		rcsnum_free(rrev);
604 	isnew = 0;
605 	if (cf->file_status == FILE_ADDED) {
606 		isnew = 1;
607 		rcsflags = RCS_CREATE;
608 		openflags = O_CREAT | O_TRUNC | O_WRONLY;
609 		if (cf->file_rcs != NULL) {
610 			if (cf->in_attic == 0)
611 				cvs_log(LP_ERR, "warning: expected %s "
612 				    "to be in the Attic", cf->file_path);
613 
614 			if (cf->file_rcs->rf_dead == 0)
615 				cvs_log(LP_ERR, "warning: expected %s "
616 				    "to be dead", cf->file_path);
617 
618 			cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
619 			(void)xsnprintf(rcsfile, MAXPATHLEN, "%s/%s%s",
620 			    repo, cf->file_name, RCS_FILE_EXT);
621 
622 			if (rename(cf->file_rpath, rcsfile) == -1)
623 				fatal("cvs_commit_local: failed to move %s "
624 				    "outside the Attic: %s", cf->file_path,
625 				    strerror(errno));
626 
627 			xfree(cf->file_rpath);
628 			cf->file_rpath = xstrdup(rcsfile);
629 
630 			rcsflags = RCS_READ | RCS_PARSE_FULLY;
631 			openflags = O_RDONLY;
632 			rcs_close(cf->file_rcs);
633 			isnew = 0;
634 		}
635 
636 		cf->repo_fd = open(cf->file_rpath, openflags);
637 		if (cf->repo_fd < 0)
638 			fatal("cvs_commit_local: %s", strerror(errno));
639 
640 		cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd,
641 		    rcsflags, 0444);
642 		if (cf->file_rcs == NULL)
643 			fatal("cvs_commit_local: failed to create RCS file "
644 			    "for %s", cf->file_path);
645 
646 		commit_desc_set(cf);
647 	}
648 
649 	if (verbosity > 1) {
650 		cvs_printf("Checking in %s:\n", cf->file_path);
651 		cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path);
652 		cvs_printf("old revision: %s; ", rbuf);
653 	}
654 
655 	if (isnew == 0 && onbranch == 0)
656 		d = commit_diff(cf, cf->file_rcs->rf_head, 0);
657 
658 	if (cf->file_status == FILE_REMOVED) {
659 		b = rcs_rev_getbuf(cf->file_rcs, crev, 0);
660 	} else if (onbranch == 1) {
661 		b = commit_diff(cf, crev, 1);
662 	} else {
663 		b = cvs_buf_load_fd(cf->fd);
664 	}
665 
666 	if (isnew == 0 && onbranch == 0) {
667 		if (rcs_deltatext_set(cf->file_rcs, crev, d) == -1)
668 			fatal("cvs_commit_local: failed to set delta");
669 	}
670 
671 	if (rcs_rev_add(cf->file_rcs, nrev, logmsg, -1, NULL) == -1)
672 		fatal("cvs_commit_local: failed to add new revision");
673 
674 	if (nrev == RCS_HEAD_REV)
675 		nrev = cf->file_rcs->rf_head;
676 
677 	if (rcs_deltatext_set(cf->file_rcs, nrev, b) == -1)
678 		fatal("cvs_commit_local: failed to set new HEAD delta");
679 
680 	if (cf->file_status == FILE_REMOVED) {
681 		if (rcs_state_set(cf->file_rcs, nrev, RCS_STATE_DEAD) == -1)
682 			fatal("cvs_commit_local: failed to set state");
683 	}
684 
685 	if (cf->file_status == FILE_ADDED && cf->file_ent->ce_opts != NULL) {
686 		int cf_kflag;
687 
688 		cf_kflag = rcs_kflag_get(cf->file_ent->ce_opts + 2);
689 		rcs_kwexp_set(cf->file_rcs, cf_kflag);
690 	}
691 
692 	rcs_write(cf->file_rcs);
693 
694 	if (cf->file_status == FILE_REMOVED) {
695 		strlcpy(nbuf, "Removed", sizeof(nbuf));
696 	} else if (cf->file_status == FILE_ADDED) {
697 		if (cf->file_rcs->rf_dead == 1)
698 			strlcpy(nbuf, "Initial Revision", sizeof(nbuf));
699 		else
700 			rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
701 	} else if (cf->file_status == FILE_MODIFIED) {
702 		rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
703 	}
704 
705 	if (verbosity > 1)
706 		cvs_printf("new revision: %s\n", nbuf);
707 
708 	(void)unlink(cf->file_path);
709 	(void)close(cf->fd);
710 	cf->fd = -1;
711 
712 	if (cf->file_status != FILE_REMOVED) {
713 		cvs_checkout_file(cf, nrev, NULL, CO_COMMIT);
714 	} else {
715 		entlist = cvs_ent_open(cf->file_wd);
716 		cvs_ent_remove(entlist, cf->file_name);
717 		cvs_ent_close(entlist, ENT_SYNC);
718 
719 		cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
720 
721 		(void)xsnprintf(attic, MAXPATHLEN, "%s/%s",
722 		    repo, CVS_PATH_ATTIC);
723 
724 		if (mkdir(attic, 0755) == -1 && errno != EEXIST)
725 			fatal("cvs_commit_local: failed to create Attic");
726 
727 		(void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo,
728 		    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
729 
730 		if (rename(cf->file_rpath, attic) == -1)
731 			fatal("cvs_commit_local: failed to move %s to Attic",
732 			    cf->file_path);
733 
734 		if (cvs_server_active == 1)
735 			cvs_server_update_entry("Remove-entry", cf);
736 	}
737 
738 	if (verbosity > 1)
739 		cvs_printf("done\n");
740 	else {
741 		cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s",
742 		    cf->file_path, rbuf, nbuf);
743 	}
744 
745 	if (line_list != NULL) {
746 		fi = xcalloc(1, sizeof(*fi));
747 		fi->file_path = xstrdup(cf->file_path);
748 		fi->crevstr = xstrdup(rbuf);
749 		fi->nrevstr = xstrdup(nbuf);
750 		TAILQ_INSERT_TAIL(&files_info, fi, flist);
751 	}
752 
753 	switch (cf->file_status) {
754 	case FILE_MODIFIED:
755 		histtype = CVS_HISTORY_COMMIT_MODIFIED;
756 		break;
757 	case FILE_ADDED:
758 		histtype = CVS_HISTORY_COMMIT_ADDED;
759 		break;
760 	case FILE_REMOVED:
761 		histtype = CVS_HISTORY_COMMIT_REMOVED;
762 		break;
763 	}
764 
765 	if (crev != NULL)
766 		rcsnum_free(crev);
767 
768 	cvs_history_add(histtype, cf, NULL);
769 }
770 
771 static BUF *
772 commit_diff(struct cvs_file *cf, RCSNUM *rev, int reverse)
773 {
774 	int fd1, fd2, f;
775 	char *p1, *p2, *p;
776 	BUF *b;
777 
778 	(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
779 
780 	if (cf->file_status == FILE_MODIFIED ||
781 	    cf->file_status == FILE_ADDED) {
782 		b = cvs_buf_load_fd(cf->fd);
783 		fd1 = cvs_buf_write_stmp(b, p1, NULL);
784 		cvs_buf_free(b);
785 	} else {
786 		fd1 = rcs_rev_write_stmp(cf->file_rcs, rev, p1, 0);
787 	}
788 
789 	(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
790 	fd2 = rcs_rev_write_stmp(cf->file_rcs, rev, p2, RCS_KWEXP_NONE);
791 
792 	b = cvs_buf_alloc(128);
793 
794 	diff_format = D_RCSDIFF;
795 
796 	if (reverse == 1) {
797 		p = p1;
798 		p1 = p2;
799 		p2 = p;
800 
801 		f = fd1;
802 		fd1 = fd2;
803 		fd2 = f;
804 	}
805 
806 	if (cvs_diffreg(p1, p2, fd1, fd2, b) == D_ERROR)
807 		fatal("commit_diff: failed to get RCS patch");
808 
809 	close(fd1);
810 	close(fd2);
811 
812 	xfree(p1);
813 	xfree(p2);
814 
815 	return (b);
816 }
817 
818 static void
819 commit_desc_set(struct cvs_file *cf)
820 {
821 	BUF *bp;
822 	int fd;
823 	char desc_path[MAXPATHLEN], *desc;
824 
825 	(void)xsnprintf(desc_path, MAXPATHLEN, "%s/%s/%s%s",
826 	    cf->file_wd, CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT);
827 
828 	if ((fd = open(desc_path, O_RDONLY)) == -1)
829 		return;
830 
831 	bp = cvs_buf_load_fd(fd);
832 	cvs_buf_putc(bp, '\0');
833 	desc = cvs_buf_release(bp);
834 
835 	rcs_desc_set(cf->file_rcs, desc);
836 
837 	(void)close(fd);
838 	(void)cvs_unlink(desc_path);
839 
840 	xfree(desc);
841 }
842