xref: /openbsd-src/usr.bin/cvs/diff.c (revision c638a04b1f53508577d23f739f44020db636f660)
1*c638a04bSjoris /*	$OpenBSD: diff.c,v 1.140 2008/06/11 20:55:34 joris Exp $	*/
208f90673Sjfb /*
3fd687d09Stobias  * Copyright (c) 2008 Tobias Stoeckmann <tobias@openbsd.org>
43ad3fb45Sjoris  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
508f90673Sjfb  *
63ad3fb45Sjoris  * Permission to use, copy, modify, and distribute this software for any
73ad3fb45Sjoris  * purpose with or without fee is hereby granted, provided that the above
83ad3fb45Sjoris  * copyright notice and this permission notice appear in all copies.
908f90673Sjfb  *
103ad3fb45Sjoris  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
113ad3fb45Sjoris  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
123ad3fb45Sjoris  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
133ad3fb45Sjoris  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
143ad3fb45Sjoris  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
153ad3fb45Sjoris  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
163ad3fb45Sjoris  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1708f90673Sjfb  */
1808f90673Sjfb 
191f8531bdSotto #include <sys/stat.h>
20fd660bf2Stobias #include <sys/time.h>
211f8531bdSotto 
221f8531bdSotto #include <errno.h>
232e0d696aSjoris #include <fcntl.h>
24fd660bf2Stobias #include <stdlib.h>
251f8531bdSotto #include <string.h>
266534056aStobias #include <time.h>
271f8531bdSotto #include <unistd.h>
2808f90673Sjfb 
2908f90673Sjfb #include "cvs.h"
30af5bb824Sniallo #include "diff.h"
319fac60a5Sjoris #include "remote.h"
3208f90673Sjfb 
333ad3fb45Sjoris void	cvs_diff_local(struct cvs_file *);
34ec6ed1abSjoris 
354230e8b3Sjoris static int	 Nflag = 0;
36fd660bf2Stobias static int	 force_head = 0;
3737fdff3fStobias static char	*koptstr;
384230e8b3Sjoris static char	*rev1 = NULL;
394230e8b3Sjoris static char	*rev2 = NULL;
40be756b91Stobias static time_t	 date1 = -1;
41be756b91Stobias static time_t	 date2 = -1;
4267caf486Sjoris 
43e4276007Sjfb struct cvs_cmd cvs_cmd_diff = {
44f331ff59Stobias 	CVS_OP_DIFF, CVS_USE_WDIR, "diff",
45e4276007Sjfb 	{ "di", "dif" },
46e4276007Sjfb 	"Show differences between revisions",
47bcf22459Stobias 	"[-cilNnpRu] [[-D date] [-r rev] [-D date2 | -r rev2]] "
48c9150269Sxsa 	"[-k mode] [file ...]",
4937fdff3fStobias 	"cfD:ik:lNnpr:Ru",
50fd660bf2Stobias 	NULL,
51fd660bf2Stobias 	cvs_diff
52fd660bf2Stobias };
53fd660bf2Stobias 
54fd660bf2Stobias struct cvs_cmd cvs_cmd_rdiff = {
55fd660bf2Stobias 	CVS_OP_RDIFF, 0, "rdiff",
56fd660bf2Stobias 	{ "patch", "pa" },
57fd660bf2Stobias 	"Show differences between revisions",
58fd660bf2Stobias 	"[-flR] [-c | -u] [-s | -t] [-V ver] -D date | -r rev\n"
59fd660bf2Stobias 	"[-D date2 | -r rev2] [-k mode] module ...",
6037fdff3fStobias 	"cfD:k:lr:RuV:",
6116cfc147Sjoris 	NULL,
623ad3fb45Sjoris 	cvs_diff
63e4276007Sjfb };
64e4276007Sjfb 
653ad3fb45Sjoris int
663ad3fb45Sjoris cvs_diff(int argc, char **argv)
6708f90673Sjfb {
68e9d83458Stobias 	int ch, flags;
693ad3fb45Sjoris 	char *arg = ".";
703ad3fb45Sjoris 	struct cvs_recursion cr;
7108f90673Sjfb 
721890abdaSjoris 	flags = CR_RECURSE_DIRS;
73bc6ad1fbStobias 	strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff",
74bc6ad1fbStobias 	    sizeof(diffargs));
75dc6a6879Sjfb 
76e9d83458Stobias 	while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ?
77e9d83458Stobias 	    cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) {
7808f90673Sjfb 		switch (ch) {
7908f90673Sjfb 		case 'c':
80f5638424Sjfb 			strlcat(diffargs, " -c", sizeof(diffargs));
81f9b67873Sniallo 			diff_format = D_CONTEXT;
8208f90673Sjfb 			break;
83be756b91Stobias 		case 'D':
84be756b91Stobias 			if (date1 == -1 && rev1 == NULL) {
85be756b91Stobias 				date1 = cvs_date_parse(optarg);
86be756b91Stobias 			} else if (date2 == -1 && rev2 == NULL) {
87be756b91Stobias 				date2 = cvs_date_parse(optarg);
88be756b91Stobias 			} else {
89be756b91Stobias 				fatal("no more than 2 revisions/dates can"
90be756b91Stobias 				    " be specified");
91be756b91Stobias 			}
92be756b91Stobias 			break;
93fd660bf2Stobias 		case 'f':
94fd660bf2Stobias 			force_head = 1;
95fd660bf2Stobias 			break;
96fd660bf2Stobias 		case 'i':
97fd660bf2Stobias 			strlcat(diffargs, " -i", sizeof(diffargs));
98fd660bf2Stobias 			diff_iflag = 1;
99fd660bf2Stobias 			break;
10037fdff3fStobias 		case 'k':
10137fdff3fStobias 			koptstr = optarg;
10237fdff3fStobias 			kflag = rcs_kflag_get(koptstr);
10337fdff3fStobias 			if (RCS_KWEXP_INVAL(kflag)) {
10437fdff3fStobias 				cvs_log(LP_ERR,
1050bc1d395Stobias 				    "invalid RCS keyword expansion mode");
106aeaaf4a6Stobias 				fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
107aeaaf4a6Stobias 				    cvs_cmd_diff.cmd_synopsis :
108aeaaf4a6Stobias 				    cvs_cmd_rdiff.cmd_synopsis);
10937fdff3fStobias 			}
11037fdff3fStobias 			break;
1111890abdaSjoris 		case 'l':
1121890abdaSjoris 			flags &= ~CR_RECURSE_DIRS;
1131890abdaSjoris 			break;
114394180a4Sjfb 		case 'n':
115394180a4Sjfb 			strlcat(diffargs, " -n", sizeof(diffargs));
116f9b67873Sniallo 			diff_format = D_RCSDIFF;
117394180a4Sjfb 			break;
11867caf486Sjoris 		case 'N':
119fd687d09Stobias 			strlcat(diffargs, " -N", sizeof(diffargs));
1202d6f447eSjoris 			Nflag = 1;
12167caf486Sjoris 			break;
122261cb0daSjoris 		case 'p':
123261cb0daSjoris 			strlcat(diffargs, " -p", sizeof(diffargs));
124261cb0daSjoris 			diff_pflag = 1;
125261cb0daSjoris 			break;
126bcf22459Stobias 		case 'R':
127bcf22459Stobias 			flags |= CR_RECURSE_DIRS;
128bcf22459Stobias 			break;
12908f90673Sjfb 		case 'r':
130be756b91Stobias 			if (date1 == -1 && rev1 == NULL) {
1314230e8b3Sjoris 				rev1 = optarg;
132be756b91Stobias 			} else if (date2 == -1 && rev2 == NULL) {
1334230e8b3Sjoris 				rev2 = optarg;
13416cfc147Sjoris 			} else {
1353ad3fb45Sjoris 				fatal("no more than 2 revisions/dates can"
13608f90673Sjfb 				    " be specified");
13708f90673Sjfb 			}
13808f90673Sjfb 			break;
13908f90673Sjfb 		case 'u':
140f5638424Sjfb 			strlcat(diffargs, " -u", sizeof(diffargs));
141f9b67873Sniallo 			diff_format = D_UNIFIED;
14208f90673Sjfb 			break;
14337fdff3fStobias 		case 'V':
14437fdff3fStobias 			fatal("the -V option is obsolete "
14537fdff3fStobias 			    "and should not be used");
14608f90673Sjfb 		default:
147aeaaf4a6Stobias 			fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
148aeaaf4a6Stobias 			    cvs_cmd_diff.cmd_synopsis :
149aeaaf4a6Stobias 			    cvs_cmd_rdiff.cmd_synopsis);
15008f90673Sjfb 		}
15108f90673Sjfb 	}
15208f90673Sjfb 
1533ad3fb45Sjoris 	argc -= optind;
1543ad3fb45Sjoris 	argv += optind;
155dc6a6879Sjfb 
1563ad3fb45Sjoris 	cr.enterdir = NULL;
1573ad3fb45Sjoris 	cr.leavedir = NULL;
1589fac60a5Sjoris 
159fd660bf2Stobias 	if (cvs_cmdop == CVS_OP_RDIFF) {
160fd660bf2Stobias 		if (rev1 == NULL)
161fd660bf2Stobias 			fatal("must specify at least one revision/date!");
162fd660bf2Stobias 
163fd687d09Stobias 		if (!argc)
164fd687d09Stobias 			fatal("%s", cvs_cmd_rdiff.cmd_synopsis);
165fd687d09Stobias 
166fd660bf2Stobias 		if (!diff_format) {
167fd660bf2Stobias 			strlcat(diffargs, " -c", sizeof(diffargs));
168fd660bf2Stobias 			diff_format = D_CONTEXT;
169fd660bf2Stobias 		}
170fd660bf2Stobias 
171fd660bf2Stobias 		flags |= CR_REPO;
172fd660bf2Stobias 	}
173fd660bf2Stobias 
1749fac60a5Sjoris 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
17580f6ca9bSjoris 		cvs_client_connect_to_server();
1769fac60a5Sjoris 		cr.fileproc = cvs_client_sendfile;
1779fac60a5Sjoris 
1789fac60a5Sjoris 		if (!(flags & CR_RECURSE_DIRS))
1799fac60a5Sjoris 			cvs_client_send_request("Argument -l");
1809fac60a5Sjoris 
18137fdff3fStobias 		if (kflag)
18237fdff3fStobias 			cvs_client_send_request("Argument -k%s", koptstr);
18337fdff3fStobias 
1849fac60a5Sjoris 		switch (diff_format) {
1859fac60a5Sjoris 		case D_CONTEXT:
1869fac60a5Sjoris 			cvs_client_send_request("Argument -c");
1879fac60a5Sjoris 			break;
1889fac60a5Sjoris 		case D_RCSDIFF:
1899fac60a5Sjoris 			cvs_client_send_request("Argument -n");
1909fac60a5Sjoris 			break;
1919fac60a5Sjoris 		case D_UNIFIED:
1929fac60a5Sjoris 			cvs_client_send_request("Argument -u");
1939fac60a5Sjoris 			break;
1949fac60a5Sjoris 		default:
1959fac60a5Sjoris 			break;
1969fac60a5Sjoris 		}
1979fac60a5Sjoris 
1989fac60a5Sjoris 		if (Nflag == 1)
1999fac60a5Sjoris 			cvs_client_send_request("Argument -N");
2009fac60a5Sjoris 
2019fac60a5Sjoris 		if (diff_pflag == 1)
2029fac60a5Sjoris 			cvs_client_send_request("Argument -p");
2039fac60a5Sjoris 
2049fac60a5Sjoris 		if (rev1 != NULL)
2059fac60a5Sjoris 			cvs_client_send_request("Argument -r%s", rev1);
2069fac60a5Sjoris 		if (rev2 != NULL)
2079fac60a5Sjoris 			cvs_client_send_request("Argument -r%s", rev2);
2089fac60a5Sjoris 	} else {
209fd660bf2Stobias 		if (cvs_cmdop == CVS_OP_RDIFF &&
210fd660bf2Stobias 		    chdir(current_cvsroot->cr_dir) == -1)
211fd660bf2Stobias 			fatal("cvs_diff: %s", strerror(errno));
212fd660bf2Stobias 
213bc5d89feSjoris 		cr.fileproc = cvs_diff_local;
2149fac60a5Sjoris 	}
2159fac60a5Sjoris 
2161890abdaSjoris 	cr.flags = flags;
217dc6a6879Sjfb 
2184230e8b3Sjoris 	diff_rev1 = diff_rev2 = NULL;
2194230e8b3Sjoris 
220fd660bf2Stobias 	if (cvs_cmdop == CVS_OP_DIFF ||
221fd660bf2Stobias 	    current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
2223ad3fb45Sjoris 		if (argc > 0)
2233ad3fb45Sjoris 			cvs_file_run(argc, argv, &cr);
224d990145dSray 		else
2253ad3fb45Sjoris 			cvs_file_run(1, &arg, &cr);
226fd660bf2Stobias 	}
227e4276007Sjfb 
2289fac60a5Sjoris 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
2299fac60a5Sjoris 		cvs_client_send_files(argv, argc);
2309fac60a5Sjoris 		cvs_client_senddir(".");
231fd660bf2Stobias 
232fd660bf2Stobias 		cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ?
233fd660bf2Stobias 		    "rdiff" : "diff");
234fd660bf2Stobias 
2359fac60a5Sjoris 		cvs_client_get_responses();
2369fac60a5Sjoris 	}
2379fac60a5Sjoris 
238e4276007Sjfb 	return (0);
239e4276007Sjfb }
240e4276007Sjfb 
24101af718aSjoris void
2423ad3fb45Sjoris cvs_diff_local(struct cvs_file *cf)
243f9b67873Sniallo {
244741443cbSjoris 	BUF *b1;
2452e0d696aSjoris 	int fd1, fd2;
2463ad3fb45Sjoris 	struct stat st;
2473ad3fb45Sjoris 	struct timeval tv[2], tv2[2];
2486534056aStobias 	struct tm datetm;
249be756b91Stobias 	char rbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ], *p1, *p2;
250d71a0c9bSxsa 
251741443cbSjoris 	b1 = NULL;
252fd687d09Stobias 	fd1 = fd2 = -1;
253fd687d09Stobias 	p1 = p2 = NULL;
254f9b67873Sniallo 
2553ad3fb45Sjoris 	cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path);
2563ad3fb45Sjoris 
2573ad3fb45Sjoris 	if (cf->file_type == CVS_DIR) {
2583ad3fb45Sjoris 		if (verbosity > 1)
2593ad3fb45Sjoris 			cvs_log(LP_NOTICE, "Diffing inside %s", cf->file_path);
2603ad3fb45Sjoris 		return;
2613ad3fb45Sjoris 	}
2623ad3fb45Sjoris 
26351ef6581Sjoris 	cvs_file_classify(cf, cvs_directory_tag);
2643ad3fb45Sjoris 
265fd687d09Stobias 	if (cvs_cmdop == CVS_OP_DIFF) {
266fd687d09Stobias 		if (cf->file_ent == NULL) {
267fd687d09Stobias 			cvs_log(LP_ERR, "I know nothing about %s",
26867caf486Sjoris 			    cf->file_path);
2693ad3fb45Sjoris 			return;
27067caf486Sjoris 		}
2713ad3fb45Sjoris 
272fd687d09Stobias 		switch (cf->file_ent->ce_status) {
273fd687d09Stobias 		case CVS_ENT_ADDED:
274fd687d09Stobias 			if (Nflag == 0) {
275fd687d09Stobias 				cvs_log(LP_ERR, "%s is a new entry, no "
276fd687d09Stobias 				    "comparison available", cf->file_path);
277fd687d09Stobias 				return;
278fd687d09Stobias 			}
279fd687d09Stobias 			if (cf->fd == -1) {
28010e77a1eStobias 				if (!cvs_server_active)
281fd687d09Stobias 					cvs_log(LP_ERR, "cannot find %s",
282fd687d09Stobias 					    cf->file_path);
283fd687d09Stobias 				return;
284fd687d09Stobias 			}
285fd687d09Stobias 			break;
286fd687d09Stobias 		case CVS_ENT_REMOVED:
287fd687d09Stobias 			if (Nflag == 0) {
288fd687d09Stobias 				cvs_log(LP_ERR, "%s was removed, no "
289fd687d09Stobias 				    "comparison available", cf->file_path);
290fd687d09Stobias 				return;
291fd687d09Stobias 			}
292fd687d09Stobias 			if (cf->file_rcs == NULL) {
293fd687d09Stobias 				cvs_log(LP_ERR, "cannot find RCS file for %s",
294fd687d09Stobias 				    cf->file_path);
295fd687d09Stobias 				return;
296fd687d09Stobias 			}
297fd687d09Stobias 			break;
298fd687d09Stobias 		default:
299*c638a04bSjoris 			if (cvs_server_active != 1 && cf->fd == -1) {
300fd687d09Stobias 				cvs_log(LP_ERR, "cannot find %s",
301fd687d09Stobias 					    cf->file_path);
302fd687d09Stobias 				return;
303fd687d09Stobias 			}
304*c638a04bSjoris 
305fd687d09Stobias 			if (cf->file_rcs == NULL) {
306fd687d09Stobias 				cvs_log(LP_ERR, "cannot find RCS file for %s",
307fd687d09Stobias 				    cf->file_path);
308fd687d09Stobias 				return;
309fd687d09Stobias 			}
310fd687d09Stobias 			break;
311fd687d09Stobias 		}
312fd687d09Stobias 	}
313fd687d09Stobias 
314be756b91Stobias 	if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL &&
315be756b91Stobias 	    date1 == -1 && date2 == -1)
316fd687d09Stobias 		return;
317fd687d09Stobias 
318fd687d09Stobias 	if (kflag && cf->file_rcs != NULL)
31937fdff3fStobias 		rcs_kwexp_set(cf->file_rcs, kflag);
32037fdff3fStobias 
321fd687d09Stobias 	if (cf->file_rcs == NULL)
322fd687d09Stobias 		diff_rev1 = NULL;
323be756b91Stobias 	else if (rev1 != NULL || date1 != -1) {
324be756b91Stobias 		cvs_specified_date = date1;
325fd687d09Stobias 		diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs);
326fd687d09Stobias 		if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) {
327be756b91Stobias 			if (rev1 != NULL)
328fd687d09Stobias 				cvs_log(LP_ERR, "tag %s not in file %s", rev1,
329fd687d09Stobias 				    cf->file_path);
330be756b91Stobias 			else {
3316534056aStobias 				gmtime_r(&cvs_specified_date, &datetm);
332be756b91Stobias 				strftime(tbuf, sizeof(tbuf),
3336534056aStobias 				    "%Y.%m.%d.%H.%M.%S", &datetm);
334be756b91Stobias 				cvs_log(LP_ERR, "no revision for date %s in "
335be756b91Stobias 				    "file %s", tbuf, cf->file_path);
336be756b91Stobias 			}
337fd687d09Stobias 			goto cleanup;
338fd687d09Stobias 		} else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
339fd687d09Stobias 		    force_head) {
340fd660bf2Stobias 			/* -f is not allowed for unknown symbols */
341fd687d09Stobias 			if ((diff_rev1 = rcsnum_parse(rev1)) == NULL)
342fd660bf2Stobias 				fatal("no such tag %s", rev1);
343fd660bf2Stobias 			rcsnum_free(diff_rev1);
344fd660bf2Stobias 
345fd687d09Stobias 			diff_rev1 = cf->file_rcs->rf_head;
346fd660bf2Stobias 		}
347be756b91Stobias 		cvs_specified_date = -1;
348fd687d09Stobias 	} else if (cvs_cmdop == CVS_OP_DIFF) {
349fd687d09Stobias 		if (cf->file_ent->ce_status == CVS_ENT_ADDED)
350fd687d09Stobias 			diff_rev1 = NULL;
351fd687d09Stobias 		else
352fd687d09Stobias 			diff_rev1 = cf->file_ent->ce_rev;
353fd660bf2Stobias 	}
35451ef6581Sjoris 
355fd687d09Stobias 	if (cf->file_rcs == NULL)
356fd687d09Stobias 		diff_rev2 = NULL;
357be756b91Stobias 	else if (rev2 != NULL || date2 != -1) {
358be756b91Stobias 		cvs_specified_date = date2;
359fd687d09Stobias 		diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs);
360fd687d09Stobias 		if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) {
361be756b91Stobias 			if (rev2 != NULL) {
362fd687d09Stobias 				cvs_log(LP_ERR, "tag %s not in file %s", rev2,
363fd687d09Stobias 				    cf->file_path);
364be756b91Stobias 			} else {
3656534056aStobias 				gmtime_r(&cvs_specified_date, &datetm);
366be756b91Stobias 				strftime(tbuf, sizeof(tbuf),
3676534056aStobias 				    "%Y.%m.%d.%H.%M.%S", &datetm);
368be756b91Stobias 				cvs_log(LP_ERR, "no revision for date %s in "
369be756b91Stobias 				    "file %s", tbuf, cf->file_path);
370be756b91Stobias 			}
371fd687d09Stobias 			goto cleanup;
372fd687d09Stobias 		} else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
373fd687d09Stobias 		    force_head) {
374fd660bf2Stobias 			/* -f is not allowed for unknown symbols */
375fd687d09Stobias 			if ((diff_rev2 = rcsnum_parse(rev2)) == NULL)
376fd660bf2Stobias 				fatal("no such tag %s", rev2);
377fd660bf2Stobias 			rcsnum_free(diff_rev2);
378fd660bf2Stobias 
379fd687d09Stobias 			diff_rev2 = cf->file_rcs->rf_head;
380fd660bf2Stobias 		}
381be756b91Stobias 		cvs_specified_date = -1;
382fd687d09Stobias 	} else if (cvs_cmdop == CVS_OP_RDIFF)
383fd687d09Stobias 		diff_rev2 = cf->file_rcs->rf_head;
384fd687d09Stobias 	else if (cf->file_ent->ce_status == CVS_ENT_REMOVED)
385fd687d09Stobias 		diff_rev2 = NULL;
386fd687d09Stobias 
387fd687d09Stobias 	if (diff_rev1 != NULL && diff_rev2 != NULL &&
388fd687d09Stobias 	    rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0)
389fd687d09Stobias 		goto cleanup;
390fd687d09Stobias 
391fd687d09Stobias 	switch (cvs_cmdop) {
392fd687d09Stobias 	case CVS_OP_DIFF:
393fd687d09Stobias 		if (cf->file_status == FILE_UPTODATE &&
394fd687d09Stobias 		    rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0) == 0)
395fd687d09Stobias 			goto cleanup;
396fd687d09Stobias 		break;
397fd687d09Stobias 	case CVS_OP_RDIFF:
398fd687d09Stobias 		if (diff_rev1 == NULL && diff_rev2 == NULL)
399fd687d09Stobias 			goto cleanup;
400fd687d09Stobias 		break;
401fd660bf2Stobias 	}
402fd660bf2Stobias 
403fd660bf2Stobias 	cvs_printf("Index: %s\n", cf->file_path);
404fd660bf2Stobias 	if (cvs_cmdop == CVS_OP_DIFF)
405fd687d09Stobias 		cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV,
406fd687d09Stobias 		    cf->file_rcs != NULL ? cf->file_rpath : cf->file_path);
407fd660bf2Stobias 
408fd660bf2Stobias 	if (diff_rev1 != NULL) {
409fd687d09Stobias 		if (cvs_cmdop == CVS_OP_DIFF && diff_rev1 != NULL) {
410fd687d09Stobias 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
411fd687d09Stobias 			cvs_printf("retrieving revision %s\n", rbuf);
412fd687d09Stobias 		}
4133ad3fb45Sjoris 
414fd687d09Stobias 		tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev1);
4153ad3fb45Sjoris 		tv[0].tv_usec = 0;
4163ad3fb45Sjoris 		tv[1] = tv[0];
417741443cbSjoris 
418fd687d09Stobias 		(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
419fd687d09Stobias 		fd1 = rcs_rev_write_stmp(cf->file_rcs, diff_rev1, p1, 0);
4202e0d696aSjoris 		if (futimes(fd1, tv) == -1)
421fd660bf2Stobias 			fatal("cvs_diff_local: utimes failed");
422fd660bf2Stobias 	}
4233ad3fb45Sjoris 
424fd687d09Stobias 	if (diff_rev2 != NULL) {
425fd687d09Stobias 		if (cvs_cmdop == CVS_OP_DIFF && rev2 != NULL) {
426fd660bf2Stobias 			(void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf));
427fd687d09Stobias 			cvs_printf("retrieving revision %s\n", rbuf);
428fd687d09Stobias 		}
4293ad3fb45Sjoris 
4303ad3fb45Sjoris 		tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2);
4313ad3fb45Sjoris 		tv2[0].tv_usec = 0;
4323ad3fb45Sjoris 		tv2[1] = tv2[0];
433741443cbSjoris 
434fd687d09Stobias 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
4352e0d696aSjoris 		fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0);
4362e0d696aSjoris 		if (futimes(fd2, tv2) == -1)
437fd660bf2Stobias 			fatal("cvs_diff_local: utimes failed");
438fd687d09Stobias 	} else if (cvs_cmdop == CVS_OP_DIFF && cf->fd != -1 &&
439fd687d09Stobias 	    cf->file_ent->ce_status != CVS_ENT_REMOVED) {
4403ad3fb45Sjoris 		if (fstat(cf->fd, &st) == -1)
4413ad3fb45Sjoris 			fatal("fstat failed %s", strerror(errno));
4429aad96bcStobias 		b1 = cvs_buf_load_fd(cf->fd);
4433ad3fb45Sjoris 
4443ad3fb45Sjoris 		tv2[0].tv_sec = st.st_mtime;
4453ad3fb45Sjoris 		tv2[0].tv_usec = 0;
4463ad3fb45Sjoris 		tv2[1] = tv2[0];
447741443cbSjoris 
448fd687d09Stobias 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
4492e0d696aSjoris 		fd2 = cvs_buf_write_stmp(b1, p2, tv2);
450741443cbSjoris 		cvs_buf_free(b1);
4513ad3fb45Sjoris 	}
4523ad3fb45Sjoris 
453fd687d09Stobias 	switch (cvs_cmdop) {
454fd687d09Stobias 	case CVS_OP_DIFF:
4553ad3fb45Sjoris 		cvs_printf("%s", diffargs);
4563ad3fb45Sjoris 
457fd687d09Stobias 		if (rev1 != NULL && diff_rev1 != NULL) {
458fd687d09Stobias 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
4593ad3fb45Sjoris 			cvs_printf(" -r%s", rbuf);
4603ad3fb45Sjoris 
461fd687d09Stobias 			if (rev2 != NULL && diff_rev2 != NULL) {
462fd660bf2Stobias 				(void)rcsnum_tostr(diff_rev2, rbuf,
463fd660bf2Stobias 				    sizeof(rbuf));
4643ad3fb45Sjoris 				cvs_printf(" -r%s", rbuf);
4653ad3fb45Sjoris 			}
46667caf486Sjoris 		}
4673ad3fb45Sjoris 
468fd660bf2Stobias 		if (diff_rev2 == NULL)
469fd687d09Stobias 			cvs_printf(" %s", cf->file_path);
47009d59ca3Stobias 		cvs_printf("\n");
471fd687d09Stobias 		break;
472fd687d09Stobias 	case CVS_OP_RDIFF:
473fd660bf2Stobias 		cvs_printf("diff ");
474fd660bf2Stobias 		switch (diff_format) {
475fd660bf2Stobias 		case D_CONTEXT:
476fd660bf2Stobias 			cvs_printf("-c ");
477fd660bf2Stobias 			break;
478fd660bf2Stobias 		case D_RCSDIFF:
479fd660bf2Stobias 			cvs_printf("-n ");
480fd660bf2Stobias 			break;
481fd660bf2Stobias 		case D_UNIFIED:
482fd660bf2Stobias 			cvs_printf("-u ");
483fd660bf2Stobias 			break;
484fd660bf2Stobias 		default:
485fd660bf2Stobias 			break;
486fd660bf2Stobias 		}
487fd660bf2Stobias 		if (diff_rev1 == NULL) {
488fd660bf2Stobias 			cvs_printf("%s ", CVS_PATH_DEVNULL);
489fd660bf2Stobias 		} else {
490fd660bf2Stobias 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
491fd660bf2Stobias 			cvs_printf("%s:%s ", cf->file_path, rbuf);
492fd660bf2Stobias 		}
4933ad3fb45Sjoris 
494fd660bf2Stobias 		if (diff_rev2 == NULL) {
495fd660bf2Stobias 			cvs_printf("%s:removed\n", cf->file_path);
496fd660bf2Stobias 		} else {
497fd660bf2Stobias 			(void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 :
498fd660bf2Stobias 			    cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
499fd660bf2Stobias 			cvs_printf("%s:%s\n", cf->file_path, rbuf);
500fd660bf2Stobias 		}
501fd687d09Stobias 		break;
502fd660bf2Stobias 	}
503fd660bf2Stobias 
504fd687d09Stobias 	if (fd1 == -1) {
505fd687d09Stobias 		if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
506fd687d09Stobias 			fatal("cannot open %s", CVS_PATH_DEVNULL);
507fd687d09Stobias 	}
508fd687d09Stobias 	if (fd2 == -1) {
509fd687d09Stobias 		if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
510fd687d09Stobias 			fatal("cannot open %s", CVS_PATH_DEVNULL);
511741443cbSjoris 	}
5123ad3fb45Sjoris 
513fd687d09Stobias 	if (cvs_diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL,
514fd687d09Stobias 	    p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL)
515fd687d09Stobias 	    == D_ERROR)
516d71a0c9bSxsa 		fatal("cvs_diff_local: failed to get RCS patch");
517d71a0c9bSxsa 
5182e0d696aSjoris 	close(fd1);
5192e0d696aSjoris 	close(fd2);
5202e0d696aSjoris 
5213ad3fb45Sjoris 	cvs_worklist_run(&temp_files, cvs_worklist_unlink);
52267caf486Sjoris 
523d71a0c9bSxsa 	if (p1 != NULL)
524d71a0c9bSxsa 		xfree(p1);
525d71a0c9bSxsa 	if (p2 != NULL)
526d71a0c9bSxsa 		xfree(p2);
527d71a0c9bSxsa 
528fd687d09Stobias cleanup:
529fd687d09Stobias 	if (diff_rev1 != NULL &&
530fd687d09Stobias 	    (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) &&
531fd687d09Stobias 	    (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev))
532fd687d09Stobias 		xfree(diff_rev1);
533fd687d09Stobias 	diff_rev1 = NULL;
534261cb0daSjoris 
535fd687d09Stobias 	if (diff_rev2 != NULL &&
536fd687d09Stobias 	    (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head))
537fd687d09Stobias 		xfree(diff_rev2);
538fd687d09Stobias 	diff_rev2 = NULL;
539f9b67873Sniallo }
540