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