xref: /openbsd-src/usr.bin/cvs/commit.c (revision 8492093369c54a4593651d5789cdec7944ae7efb)
1 /*	$OpenBSD: commit.c,v 1.135 2008/06/08 20:08:43 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 	char rev[CVS_REV_BUFSZ];
248 	struct cvs_dirlist *d;
249 
250 	branch = brev = NULL;
251 
252 	cvs_log(LP_TRACE, "cvs_commit_check_files(%s)", cf->file_path);
253 
254 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL)
255 		cvs_remote_classify_file(cf);
256 	else
257 		cvs_file_classify(cf, cvs_directory_tag);
258 
259 	if (cf->file_type == CVS_DIR) {
260 		if (verbosity > 1)
261 			cvs_log(LP_NOTICE, "Examining %s", cf->file_path);
262 		return;
263 	}
264 
265 	if (cf->file_status == FILE_CONFLICT ||
266 	    cf->file_status == FILE_UNLINK) {
267 		conflicts_found++;
268 		return;
269 	}
270 
271 	if (cf->file_status != FILE_REMOVED &&
272 	    update_has_conflict_markers(cf)) {
273 		cvs_log(LP_ERR, "conflict: unresolved conflicts in %s from "
274 		    "merging, please fix these first", cf->file_path);
275 		conflicts_found++;
276 		return;
277 	}
278 
279 	if (cf->file_status == FILE_MERGE ||
280 	    cf->file_status == FILE_PATCH ||
281 	    cf->file_status == FILE_CHECKOUT ||
282 	    cf->file_status == FILE_LOST) {
283 		cvs_log(LP_ERR, "conflict: %s is not up-to-date",
284 		    cf->file_path);
285 		conflicts_found++;
286 		return;
287 	}
288 
289 	if (cf->file_ent != NULL && cf->file_ent->ce_date != -1) {
290 		cvs_log(LP_ERR, "conflict: cannot commit to sticky date for %s",
291 		    cf->file_path);
292 		conflicts_found++;
293 		return;
294 	}
295 
296 	if (current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
297 		tag = cvs_directory_tag;
298 		if (cf->file_ent != NULL)
299 			tag = cf->file_ent->ce_tag;
300 
301 		if (tag != NULL && cf->file_rcs != NULL) {
302 			brev = rcs_sym_getrev(cf->file_rcs, tag);
303 			if (brev != NULL) {
304 				if (RCSNUM_ISBRANCH(brev))
305 					goto next;
306 				rcsnum_free(brev);
307 			}
308 
309 			brev = rcs_translate_tag(tag, cf->file_rcs);
310 
311 			if (brev == NULL) {
312 				fatal("failed to resolve tag: %s",
313 				    cf->file_ent->ce_tag);
314 			}
315 
316 			rcsnum_tostr(brev, rev, sizeof(rev));
317 			if ((branch = rcsnum_revtobr(brev)) == NULL) {
318 				cvs_log(LP_ERR, "%s is not a branch revision",
319 				    rev);
320 				conflicts_found++;
321 				rcsnum_free(brev);
322 				return;
323 			}
324 
325 			if (!RCSNUM_ISBRANCHREV(brev)) {
326 				cvs_log(LP_ERR, "%s is not a branch revision",
327 				    rev);
328 				conflicts_found++;
329 				rcsnum_free(branch);
330 				rcsnum_free(brev);
331 				return;
332 			}
333 
334 			rcsnum_tostr(branch, rev, sizeof(rev));
335 			if (!RCSNUM_ISBRANCH(branch)) {
336 				cvs_log(LP_ERR, "%s (%s) is not a branch",
337 				    cf->file_ent->ce_tag, rev);
338 				conflicts_found++;
339 				rcsnum_free(branch);
340 				rcsnum_free(brev);
341 				return;
342 			}
343 		}
344 	}
345 
346 next:
347 	if (branch != NULL)
348 		rcsnum_free(branch);
349 	if (brev != NULL)
350 		rcsnum_free(brev);
351 
352 	if (cf->file_status != FILE_ADDED &&
353 	    cf->file_status != FILE_REMOVED &&
354 	    cf->file_status != FILE_MODIFIED)
355 		return;
356 
357 	if (current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
358 		d = cvs_commit_getdir(cf->file_wd);
359 	} else {
360 		d = cvs_commit_getdir("remote");
361 	}
362 
363 	cvs_file_get(cf->file_path, 0, &d->files_affected);
364 
365 	switch (cf->file_status) {
366 	case FILE_ADDED:
367 		cvs_file_get(cf->file_path, 0, &d->files_added);
368 		break;
369 	case FILE_REMOVED:
370 		cvs_file_get(cf->file_path, 0, &d->files_removed);
371 		break;
372 	case FILE_MODIFIED:
373 		cvs_file_get(cf->file_path, 0, &d->files_modified);
374 		break;
375 	}
376 }
377 
378 void
379 cvs_commit_local(struct cvs_file *cf)
380 {
381 	char *tag;
382 	BUF *b, *d;
383 	int onbranch, isnew, histtype;
384 	RCSNUM *nrev, *crev, *rrev, *brev;
385 	int openflags, rcsflags;
386 	char rbuf[CVS_REV_BUFSZ], nbuf[CVS_REV_BUFSZ];
387 	CVSENTRIES *entlist;
388 	char attic[MAXPATHLEN], repo[MAXPATHLEN], rcsfile[MAXPATHLEN];
389 
390 	cvs_log(LP_TRACE, "cvs_commit_local(%s)", cf->file_path);
391 	cvs_file_classify(cf, cvs_directory_tag);
392 
393 	if (cvs_noexec == 1)
394 		return;
395 
396 	if (cf->file_type != CVS_FILE)
397 		fatal("cvs_commit_local: '%s' is not a file", cf->file_path);
398 
399 	if (cf->file_status != FILE_MODIFIED &&
400 	    cf->file_status != FILE_ADDED &&
401 	    cf->file_status != FILE_REMOVED) {
402 		cvs_log(LP_ERR, "skipping bogus file `%s'", cf->file_path);
403 		return;
404 	}
405 
406 	onbranch = 0;
407 	nrev = RCS_HEAD_REV;
408 	crev = NULL;
409 	rrev = NULL;
410 
411 	if (cf->file_rcs != NULL && cf->file_rcs->rf_branch != NULL) {
412 		rcsnum_free(cf->file_rcs->rf_branch);
413 		cf->file_rcs->rf_branch = NULL;
414 	}
415 
416 	if (cf->file_status == FILE_MODIFIED ||
417 	    cf->file_status == FILE_REMOVED || (cf->file_status == FILE_ADDED
418 	    && cf->file_rcs != NULL && cf->file_rcs->rf_dead == 1)) {
419 		rrev = rcs_head_get(cf->file_rcs);
420 		crev = rcs_head_get(cf->file_rcs);
421 		if (crev == NULL || rrev == NULL)
422 			fatal("RCS head empty or missing in %s\n",
423 			    cf->file_rcs->rf_path);
424 
425 		tag = cvs_directory_tag;
426 		if (cf->file_ent != NULL && cf->file_ent->ce_tag != NULL)
427 			tag = cf->file_ent->ce_tag;
428 
429 		if (tag != NULL) {
430 			rcsnum_free(crev);
431 			rcsnum_free(rrev);
432 			brev = rcs_sym_getrev(cf->file_rcs, tag);
433 			crev = rcs_translate_tag(tag, cf->file_rcs);
434 			if (brev == NULL || crev == NULL) {
435 				fatal("failed to resolve existing tag: %s",
436 				    tag);
437 			}
438 
439 			rrev = rcsnum_alloc();
440 			rcsnum_cpy(brev, rrev, brev->rn_len - 1);
441 
442 			if (RCSNUM_ISBRANCHREV(crev) &&
443 			    rcsnum_cmp(crev, rrev, 0)) {
444 				nrev = rcsnum_alloc();
445 				rcsnum_cpy(crev, nrev, 0);
446 				rcsnum_inc(nrev);
447 			} else if (!RCSNUM_ISBRANCH(crev)) {
448 				nrev = rcsnum_brtorev(brev);
449 				if (nrev == NULL)
450 					fatal("failed to create branch rev");
451 			} else {
452 				fatal("this isnt suppose to happen, honestly");
453 			}
454 
455 			rcsnum_free(brev);
456 			rcsnum_free(rrev);
457 			rrev = rcsnum_branch_root(nrev);
458 
459 			/* branch stuff was checked in cvs_commit_check_files */
460 			onbranch = 1;
461 		}
462 
463 		rcsnum_tostr(crev, rbuf, sizeof(rbuf));
464 	} else {
465 		strlcpy(rbuf, "Non-existent", sizeof(rbuf));
466 	}
467 
468 	if (rrev != NULL)
469 		rcsnum_free(rrev);
470 	isnew = 0;
471 	if (cf->file_status == FILE_ADDED) {
472 		isnew = 1;
473 		rcsflags = RCS_CREATE;
474 		openflags = O_CREAT | O_TRUNC | O_WRONLY;
475 		if (cf->file_rcs != NULL) {
476 			if (cf->in_attic == 0)
477 				cvs_log(LP_ERR, "warning: expected %s "
478 				    "to be in the Attic", cf->file_path);
479 
480 			if (cf->file_rcs->rf_dead == 0)
481 				cvs_log(LP_ERR, "warning: expected %s "
482 				    "to be dead", cf->file_path);
483 
484 			cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
485 			(void)xsnprintf(rcsfile, MAXPATHLEN, "%s/%s%s",
486 			    repo, cf->file_name, RCS_FILE_EXT);
487 
488 			if (rename(cf->file_rpath, rcsfile) == -1)
489 				fatal("cvs_commit_local: failed to move %s "
490 				    "outside the Attic: %s", cf->file_path,
491 				    strerror(errno));
492 
493 			xfree(cf->file_rpath);
494 			cf->file_rpath = xstrdup(rcsfile);
495 
496 			rcsflags = RCS_READ | RCS_PARSE_FULLY;
497 			openflags = O_RDONLY;
498 			rcs_close(cf->file_rcs);
499 			isnew = 0;
500 		}
501 
502 		cf->repo_fd = open(cf->file_rpath, openflags);
503 		if (cf->repo_fd < 0)
504 			fatal("cvs_commit_local: %s", strerror(errno));
505 
506 		cf->file_rcs = rcs_open(cf->file_rpath, cf->repo_fd,
507 		    rcsflags, 0444);
508 		if (cf->file_rcs == NULL)
509 			fatal("cvs_commit_local: failed to create RCS file "
510 			    "for %s", cf->file_path);
511 
512 		commit_desc_set(cf);
513 	}
514 
515 	if (verbosity > 1) {
516 		cvs_printf("Checking in %s:\n", cf->file_path);
517 		cvs_printf("%s <- %s\n", cf->file_rpath, cf->file_path);
518 		cvs_printf("old revision: %s; ", rbuf);
519 	}
520 
521 	if (isnew == 0 && onbranch == 0)
522 		d = commit_diff(cf, cf->file_rcs->rf_head, 0);
523 
524 	if (cf->file_status == FILE_REMOVED) {
525 		b = rcs_rev_getbuf(cf->file_rcs, crev, 0);
526 	} else if (onbranch == 1) {
527 		b = commit_diff(cf, crev, 1);
528 	} else {
529 		b = cvs_buf_load_fd(cf->fd);
530 	}
531 
532 	if (isnew == 0 && onbranch == 0) {
533 		if (rcs_deltatext_set(cf->file_rcs, crev, d) == -1)
534 			fatal("cvs_commit_local: failed to set delta");
535 	}
536 
537 	if (rcs_rev_add(cf->file_rcs, nrev, logmsg, -1, NULL) == -1)
538 		fatal("cvs_commit_local: failed to add new revision");
539 
540 	if (nrev == RCS_HEAD_REV)
541 		nrev = cf->file_rcs->rf_head;
542 
543 	if (rcs_deltatext_set(cf->file_rcs, nrev, b) == -1)
544 		fatal("cvs_commit_local: failed to set new HEAD delta");
545 
546 	if (cf->file_status == FILE_REMOVED) {
547 		if (rcs_state_set(cf->file_rcs, nrev, RCS_STATE_DEAD) == -1)
548 			fatal("cvs_commit_local: failed to set state");
549 	}
550 
551 	if (cf->file_status == FILE_ADDED && cf->file_ent->ce_opts != NULL) {
552 		int cf_kflag;
553 
554 		cf_kflag = rcs_kflag_get(cf->file_ent->ce_opts + 2);
555 		rcs_kwexp_set(cf->file_rcs, cf_kflag);
556 	}
557 
558 	rcs_write(cf->file_rcs);
559 
560 	if (cf->file_status == FILE_REMOVED) {
561 		strlcpy(nbuf, "Removed", sizeof(nbuf));
562 	} else if (cf->file_status == FILE_ADDED) {
563 		if (cf->file_rcs->rf_dead == 1)
564 			strlcpy(nbuf, "Initial Revision", sizeof(nbuf));
565 		else
566 			rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
567 	} else if (cf->file_status == FILE_MODIFIED) {
568 		rcsnum_tostr(nrev, nbuf, sizeof(nbuf));
569 	}
570 
571 	if (verbosity > 1)
572 		cvs_printf("new revision: %s\n", nbuf);
573 
574 	(void)unlink(cf->file_path);
575 	(void)close(cf->fd);
576 	cf->fd = -1;
577 
578 	if (cf->file_status != FILE_REMOVED) {
579 		cvs_checkout_file(cf, nrev, NULL, CO_COMMIT);
580 	} else {
581 		entlist = cvs_ent_open(cf->file_wd);
582 		cvs_ent_remove(entlist, cf->file_name);
583 		cvs_ent_close(entlist, ENT_SYNC);
584 
585 		cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
586 
587 		(void)xsnprintf(attic, MAXPATHLEN, "%s/%s",
588 		    repo, CVS_PATH_ATTIC);
589 
590 		if (mkdir(attic, 0755) == -1 && errno != EEXIST)
591 			fatal("cvs_commit_local: failed to create Attic");
592 
593 		(void)xsnprintf(attic, MAXPATHLEN, "%s/%s/%s%s", repo,
594 		    CVS_PATH_ATTIC, cf->file_name, RCS_FILE_EXT);
595 
596 		if (rename(cf->file_rpath, attic) == -1)
597 			fatal("cvs_commit_local: failed to move %s to Attic",
598 			    cf->file_path);
599 
600 		if (cvs_server_active == 1)
601 			cvs_server_update_entry("Remove-entry", cf);
602 	}
603 
604 	if (verbosity > 1)
605 		cvs_printf("done\n");
606 	else {
607 		cvs_log(LP_NOTICE, "checking in '%s'; revision %s -> %s",
608 		    cf->file_path, rbuf, nbuf);
609 	}
610 
611 	switch (cf->file_status) {
612 	case FILE_MODIFIED:
613 		histtype = CVS_HISTORY_COMMIT_MODIFIED;
614 		break;
615 	case FILE_ADDED:
616 		histtype = CVS_HISTORY_COMMIT_ADDED;
617 		break;
618 	case FILE_REMOVED:
619 		histtype = CVS_HISTORY_COMMIT_REMOVED;
620 		break;
621 	}
622 
623 	if (crev != NULL)
624 		rcsnum_free(crev);
625 
626 	cvs_history_add(histtype, cf, NULL);
627 }
628 
629 static BUF *
630 commit_diff(struct cvs_file *cf, RCSNUM *rev, int reverse)
631 {
632 	int fd1, fd2, f;
633 	char *p1, *p2, *p;
634 	BUF *b;
635 
636 	(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
637 
638 	if (cf->file_status == FILE_MODIFIED ||
639 	    cf->file_status == FILE_ADDED) {
640 		b = cvs_buf_load_fd(cf->fd);
641 		fd1 = cvs_buf_write_stmp(b, p1, NULL);
642 		cvs_buf_free(b);
643 	} else {
644 		fd1 = rcs_rev_write_stmp(cf->file_rcs, rev, p1, 0);
645 	}
646 
647 	(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
648 	fd2 = rcs_rev_write_stmp(cf->file_rcs, rev, p2, RCS_KWEXP_NONE);
649 
650 	b = cvs_buf_alloc(128);
651 
652 	diff_format = D_RCSDIFF;
653 
654 	if (reverse == 1) {
655 		p = p1;
656 		p1 = p2;
657 		p2 = p;
658 
659 		f = fd1;
660 		fd1 = fd2;
661 		fd2 = f;
662 	}
663 
664 	if (cvs_diffreg(p1, p2, fd1, fd2, b) == D_ERROR)
665 		fatal("commit_diff: failed to get RCS patch");
666 
667 	close(fd1);
668 	close(fd2);
669 
670 	xfree(p1);
671 	xfree(p2);
672 
673 	return (b);
674 }
675 
676 static void
677 commit_desc_set(struct cvs_file *cf)
678 {
679 	BUF *bp;
680 	int fd;
681 	char desc_path[MAXPATHLEN], *desc;
682 
683 	(void)xsnprintf(desc_path, MAXPATHLEN, "%s/%s/%s%s",
684 	    cf->file_wd, CVS_PATH_CVSDIR, cf->file_name, CVS_DESCR_FILE_EXT);
685 
686 	if ((fd = open(desc_path, O_RDONLY)) == -1)
687 		return;
688 
689 	bp = cvs_buf_load_fd(fd);
690 	cvs_buf_putc(bp, '\0');
691 	desc = cvs_buf_release(bp);
692 
693 	rcs_desc_set(cf->file_rcs, desc);
694 
695 	(void)close(fd);
696 	(void)cvs_unlink(desc_path);
697 
698 	xfree(desc);
699 }
700