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