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