xref: /openbsd-src/usr.bin/cvs/commit.c (revision d4741794dd2f512d997014f8bd85fbb24d935059)
1 /*	$OpenBSD: commit.c,v 1.155 2016/10/13 20:51:25 fcambus 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 				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 				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 				free(branch);
395 				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 				free(branch);
405 				free(brev);
406 				return;
407 			}
408 		}
409 	}
410 
411 next:
412 	free(branch);
413 	free(brev);
414 
415 	if (cf->file_status != FILE_ADDED &&
416 	    cf->file_status != FILE_REMOVED &&
417 	    cf->file_status != FILE_MODIFIED)
418 		return;
419 
420 	cvs_file_get(cf->file_path, 0, &files_affected, CVS_FILE);
421 
422 	switch (cf->file_status) {
423 	case FILE_ADDED:
424 		cvs_file_get(cf->file_path, 0, &files_added, CVS_FILE);
425 		break;
426 	case FILE_REMOVED:
427 		cvs_file_get(cf->file_path, 0, &files_removed, CVS_FILE);
428 		break;
429 	case FILE_MODIFIED:
430 		cvs_file_get(cf->file_path, 0, &files_modified, CVS_FILE);
431 		break;
432 	}
433 }
434 
435 void
436 cvs_commit_local(struct cvs_file *cf)
437 {
438 	char *tag;
439 	BUF *b, *d;
440 	int onbranch, isnew, histtype, branchadded;
441 	RCSNUM *nrev, *crev, *rrev, *brev;
442 	int openflags, rcsflags;
443 	char rbuf[CVS_REV_BUFSZ], nbuf[CVS_REV_BUFSZ];
444 	CVSENTRIES *entlist;
445 	char attic[PATH_MAX], repo[PATH_MAX], rcsfile[PATH_MAX];
446 	struct file_info *fi;
447 
448 	cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path);
449 	cvs_file_classify(cf, cvs_directory_tag);
450 
451 	if (cvs_noexec == 1)
452 		return;
453 
454 	if (cf->file_type != CVS_FILE)
455 		fatal("cvs_commit_local: '%s' is not a file", cf->file_path);
456 
457 	tag = cvs_directory_tag;
458 	if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL)
459 		tag = cf->file_ent->ce_tag;
460 
461 	branchadded = 0;
462 	switch (cf->file_status) {
463 	case FILE_ADDED:
464 		if (cf->file_rcs == NULL && tag != NULL) {
465 			branchadded = 1;
466 			cvs_add_tobranch(cf, tag);
467 		}
468 		break;
469 	case FILE_MODIFIED:
470 	case FILE_REMOVED:
471 		if (cf->file_rcs == NULL) {
472 			cvs_log(LP_ERR, "RCS file for %s got lost",
473 			    cf->file_path);
474 			return;
475 		}
476 		break;
477 	default:
478 		cvs_log(LP_ERR, "skipping bogus file `%s'", cf->file_path);
479 		return;
480 	}
481 
482 	onbranch = 0;
483 	nrev = RCS_HEAD_REV;
484 	crev = NULL;
485 	rrev = NULL;
486 	d = NULL;
487 
488 	if (cf->file_rcs != NULL && cf->file_rcs->rf_branch != NULL) {
489 		free(cf->file_rcs->rf_branch);
490 		cf->file_rcs->rf_branch = NULL;
491 	}
492 
493 	if (cf->file_rcs != NULL) {
494 		rrev = rcs_head_get(cf->file_rcs);
495 		crev = rcs_head_get(cf->file_rcs);
496 		if (crev == NULL || rrev == NULL)
497 			fatal("no head revision in RCS file for %s",
498 			    cf->file_path);
499 
500 		if (tag != NULL) {
501 			free(crev);
502 			free(rrev);
503 			brev = rcs_sym_getrev(cf->file_rcs, tag);
504 			crev = rcs_translate_tag(tag, cf->file_rcs);
505 			if (brev == NULL || crev == NULL) {
506 				fatal("failed to resolve existing tag: %s",
507 				    tag);
508 			}
509 
510 			rrev = rcsnum_alloc();
511 			rcsnum_cpy(brev, rrev, brev->rn_len - 1);
512 
513 			if (RCSNUM_ISBRANCHREV(crev) &&
514 			    rcsnum_cmp(crev, rrev, 0)) {
515 				nrev = rcsnum_alloc();
516 				rcsnum_cpy(crev, nrev, 0);
517 				rcsnum_inc(nrev);
518 			} else if (!RCSNUM_ISBRANCH(crev)) {
519 				nrev = rcsnum_brtorev(brev);
520 				if (nrev == NULL)
521 					fatal("failed to create branch rev");
522 			} else {
523 				fatal("this isnt suppose to happen, honestly");
524 			}
525 
526 			free(brev);
527 			free(rrev);
528 			rrev = rcsnum_branch_root(nrev);
529 
530 			/* branch stuff was checked in cvs_commit_check_files */
531 			onbranch = 1;
532 		}
533 
534 		rcsnum_tostr(crev, rbuf, sizeof(rbuf));
535 	} else {
536 		strlcpy(rbuf, "Non-existent", sizeof(rbuf));
537 	}
538 
539 	free(rrev);
540 	isnew = 0;
541 	if (cf->file_status == FILE_ADDED) {
542 		isnew = 1;
543 		rcsflags = RCS_CREATE;
544 		openflags = O_CREAT | O_RDONLY;
545 		if (cf->file_rcs != NULL) {
546 			if (!onbranch) {
547 				if (cf->in_attic == 0)
548 					cvs_log(LP_ERR, "warning: expected %s "
549 					    "to be in the Attic",
550 					    cf->file_path);
551 
552 				if (cf->file_rcs->rf_dead == 0)
553 					cvs_log(LP_ERR, "warning: expected %s "
554 					    "to be dead", cf->file_path);
555 
556 				cvs_get_repository_path(cf->file_wd, repo,
557 				    PATH_MAX);
558 				(void)xsnprintf(rcsfile, PATH_MAX, "%s/%s%s",
559 				    repo, cf->file_name, RCS_FILE_EXT);
560 
561 				if (rename(cf->file_rpath, rcsfile) == -1)
562 					fatal("cvs_commit_local: failed to "
563 					    "move %s outside the Attic: %s",
564 					    cf->file_path, strerror(errno));
565 
566 				free(cf->file_rpath);
567 				cf->file_rpath = xstrdup(rcsfile);
568 				isnew = 0;
569 			}
570 
571 			rcsflags = RCS_READ | RCS_PARSE_FULLY;
572 			openflags = O_RDONLY;
573 			rcs_close(cf->file_rcs);
574 		}
575 
576 		cf->repo_fd = open(cf->file_rpath, openflags);
577 		if (cf->repo_fd < 0)
578 			fatal("cvs_commit_local: %s", strerror(errno));
579 
580 		cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd,
581 		    rcsflags, 0444);
582 		if (cf->file_rcs == NULL)
583 			fatal("cvs_commit_local: failed to create RCS file "
584 			    "for %s", cf->file_path);
585 
586 		commit_desc_set(cf);
587 
588 		if (branchadded)
589 			strlcpy(rbuf, "Non-existent", sizeof(rbuf));
590 	}
591 
592 	if (verbosity > 1) {
593 		cvs_printf("Checking in %s:\n", cf->file_path);
594 		cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path);
595 		cvs_printf("old revision: %s; ", rbuf);
596 	}
597 
598 	if (isnew == 0 && cf->file_rcs->rf_head == NULL)
599 		fatal("no head revision in RCS file for %s", cf->file_path);
600 
601 	if (isnew == 0 && onbranch == 0)
602 		d = commit_diff(cf, cf->file_rcs->rf_head, 0);
603 
604 	if (cf->file_status == FILE_REMOVED) {
605 		b = rcs_rev_getbuf(cf->file_rcs, crev, 0);
606 	} else if (onbranch == 1) {
607 		b = commit_diff(cf, crev, 1);
608 	} else {
609 		b = buf_load_fd(cf->fd);
610 	}
611 
612 	if (isnew == 0 && onbranch == 0) {
613 		if (rcs_deltatext_set(cf->file_rcs, crev, d) == -1)
614 			fatal("cvs_commit_local: failed to set delta");
615 	}
616 
617 	if (rcs_rev_add(cf->file_rcs, nrev, logmsg, -1, NULL) == -1)
618 		fatal("cvs_commit_local: failed to add new revision");
619 
620 	if (nrev == RCS_HEAD_REV)
621 		nrev = cf->file_rcs->rf_head;
622 
623 	if (rcs_deltatext_set(cf->file_rcs, nrev, b) == -1)
624 		fatal("cvs_commit_local: failed to set new HEAD delta");
625 
626 	if (cf->file_status == FILE_REMOVED) {
627 		if (rcs_state_set(cf->file_rcs, nrev, RCS_STATE_DEAD) == -1)
628 			fatal("cvs_commit_local: failed to set state");
629 	}
630 
631 	if (cf->file_status == FILE_ADDED && cf->file_ent->ce_opts != NULL) {
632 		int cf_kflag;
633 
634 		cf_kflag = rcs_kflag_get(cf->file_ent->ce_opts + 2);
635 		rcs_kwexp_set(cf->file_rcs, cf_kflag);
636 	}
637 
638 	rcs_write(cf->file_rcs);
639 
640 	if (cf->file_status == FILE_REMOVED) {
641 		strlcpy(nbuf, "Removed", sizeof(nbuf));
642 	} else if (cf->file_status == FILE_ADDED) {
643 		if (cf->file_rcs->rf_dead == 1)
644 			strlcpy(nbuf, "Initial Revision", sizeof(nbuf));
645 		else
646 			rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
647 	} else if (cf->file_status == FILE_MODIFIED) {
648 		rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
649 	}
650 
651 	if (verbosity > 1)
652 		cvs_printf("new revision: %s\n", nbuf);
653 
654 	(void)unlink(cf->file_path);
655 	(void)close(cf->fd);
656 	cf->fd = -1;
657 
658 	if (cf->file_status != FILE_REMOVED) {
659 		cvs_checkout_file(cf, nrev, NULL, CO_COMMIT);
660 	} else {
661 		entlist = cvs_ent_open(cf->file_wd);
662 		cvs_ent_remove(entlist, cf->file_name);
663 
664 		cvs_get_repository_path(cf->file_wd, repo, PATH_MAX);
665 
666 		(void)xsnprintf(attic, PATH_MAX, "%s/%s",
667 		    repo, CVS_PATH_ATTIC);
668 
669 		if (mkdir(attic, 0755) == -1 && errno != EEXIST)
670 			fatal("cvs_commit_local: failed to create Attic");
671 
672 		(void)xsnprintf(attic, PATH_MAX, "%s/%s/%s%s", repo,
673 		    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
674 
675 		if (rename(cf->file_rpath, attic) == -1)
676 			fatal("cvs_commit_local: failed to move %s to Attic",
677 			    cf->file_path);
678 
679 		if (cvs_server_active == 1)
680 			cvs_server_update_entry("Remove-entry", cf);
681 	}
682 
683 	if (verbosity > 1)
684 		cvs_printf("done\n");
685 	else {
686 		cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s",
687 		    cf->file_path, rbuf, nbuf);
688 	}
689 
690 	if (line_list != NULL) {
691 		fi = xcalloc(1, sizeof(*fi));
692 		fi->file_path = xstrdup(cf->file_path);
693 		fi->crevstr = xstrdup(rbuf);
694 		fi->nrevstr = xstrdup(nbuf);
695 		if (tag != NULL)
696 			fi->tag_new = xstrdup(tag);
697 		TAILQ_INSERT_TAIL(&files_info, fi, flist);
698 	}
699 
700 	switch (cf->file_status) {
701 	case FILE_MODIFIED:
702 		histtype = CVS_HISTORY_COMMIT_MODIFIED;
703 		break;
704 	case FILE_ADDED:
705 		histtype = CVS_HISTORY_COMMIT_ADDED;
706 		break;
707 	case FILE_REMOVED:
708 		histtype = CVS_HISTORY_COMMIT_REMOVED;
709 		break;
710 	default:
711 		histtype = -1;
712 		break;
713 	}
714 
715 	free(crev);
716 
717 	if (histtype != -1)
718 		cvs_history_add(histtype, cf, NULL);
719 	else
720 		cvs_log(LP_NOTICE, "histtype was -1 for %s", cf->file_path);
721 }
722 
723 static BUF *
724 commit_diff(struct cvs_file *cf, RCSNUM *rev, int reverse)
725 {
726 	int fd1, fd2, d;
727 	char *p1, *p2;
728 	BUF *b;
729 
730 	(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
731 
732 	if (cf->file_status == FILE_MODIFIED ||
733 	    cf->file_status == FILE_ADDED) {
734 		b = buf_load_fd(cf->fd);
735 		fd1 = buf_write_stmp(b, p1, NULL);
736 		buf_free(b);
737 	} else {
738 		fd1 = rcs_rev_write_stmp(cf->file_rcs, rev, p1, 0);
739 	}
740 
741 	(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
742 	fd2 = rcs_rev_write_stmp(cf->file_rcs, rev, p2, RCS_KWEXP_NONE);
743 
744 	b = buf_alloc(128);
745 
746 	diff_format = D_RCSDIFF;
747 
748 	if (reverse == 1)
749 		d = diffreg(p2, p1, fd2, fd1, b, D_FORCEASCII);
750 	else
751 		d = diffreg(p1, p2, fd1, fd2, b, D_FORCEASCII);
752 	if (d == D_ERROR)
753 		fatal("commit_diff: failed to get RCS patch");
754 
755 	close(fd1);
756 	close(fd2);
757 
758 	free(p1);
759 	free(p2);
760 
761 	return (b);
762 }
763 
764 static void
765 commit_desc_set(struct cvs_file *cf)
766 {
767 	BUF *bp;
768 	int fd;
769 	char desc_path[PATH_MAX], *desc;
770 
771 	(void)xsnprintf(desc_path, PATH_MAX, "%s/%s/%s%s",
772 	    cf->file_wd, CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT);
773 
774 	if ((fd = open(desc_path, O_RDONLY)) == -1)
775 		return;
776 
777 	bp = buf_load_fd(fd);
778 	buf_putc(bp, '\0');
779 	desc = buf_release(bp);
780 
781 	rcs_desc_set(cf->file_rcs, desc);
782 
783 	(void)close(fd);
784 	(void)cvs_unlink(desc_path);
785 
786 	free(desc);
787 }
788