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