xref: /openbsd-src/usr.bin/cvs/diff.c (revision 092db204ab3eaa831b770cee34445d3a12641812)
1*092db204Sray /*	$OpenBSD: diff.c,v 1.159 2010/07/30 21:47:18 ray 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 
35219c50abSray static int	 dflags = 0;
364230e8b3Sjoris static int	 Nflag = 0;
37fd660bf2Stobias static int	 force_head = 0;
3837fdff3fStobias static char	*koptstr;
394230e8b3Sjoris static char	*rev1 = NULL;
404230e8b3Sjoris static char	*rev2 = NULL;
41be756b91Stobias static time_t	 date1 = -1;
42be756b91Stobias static time_t	 date2 = -1;
43909bf3e0Stobias static char	*dateflag1 = NULL;
44909bf3e0Stobias static char	*dateflag2 = NULL;
4567caf486Sjoris 
46e4276007Sjfb struct cvs_cmd cvs_cmd_diff = {
47f331ff59Stobias 	CVS_OP_DIFF, CVS_USE_WDIR, "diff",
48e4276007Sjfb 	{ "di", "dif" },
49e4276007Sjfb 	"Show differences between revisions",
506412cad2Ssthen 	"[-abcdilNnpRuw] [[-D date] [-r rev] [-D date2 | -r rev2]] "
51c9150269Sxsa 	"[-k mode] [file ...]",
5272026f1aSnicm 	"abcfC:dD:ik:lNnpr:RuU:w",
53fd660bf2Stobias 	NULL,
54fd660bf2Stobias 	cvs_diff
55fd660bf2Stobias };
56fd660bf2Stobias 
57fd660bf2Stobias struct cvs_cmd cvs_cmd_rdiff = {
58fd660bf2Stobias 	CVS_OP_RDIFF, 0, "rdiff",
59fd660bf2Stobias 	{ "patch", "pa" },
60fd660bf2Stobias 	"Show differences between revisions",
61fd660bf2Stobias 	"[-flR] [-c | -u] [-s | -t] [-V ver] -D date | -r rev\n"
62fd660bf2Stobias 	"[-D date2 | -r rev2] [-k mode] module ...",
6337fdff3fStobias 	"cfD:k:lr:RuV:",
6416cfc147Sjoris 	NULL,
653ad3fb45Sjoris 	cvs_diff
66e4276007Sjfb };
67e4276007Sjfb 
683ad3fb45Sjoris int
693ad3fb45Sjoris cvs_diff(int argc, char **argv)
7008f90673Sjfb {
71e9d83458Stobias 	int ch, flags;
723ad3fb45Sjoris 	char *arg = ".";
7372026f1aSnicm 	const char *errstr;
743ad3fb45Sjoris 	struct cvs_recursion cr;
7508f90673Sjfb 
761890abdaSjoris 	flags = CR_RECURSE_DIRS;
77bc6ad1fbStobias 	strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff",
78bc6ad1fbStobias 	    sizeof(diffargs));
79dc6a6879Sjfb 
80e9d83458Stobias 	while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ?
81e9d83458Stobias 	    cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) {
8208f90673Sjfb 		switch (ch) {
836412cad2Ssthen 		case 'a':
846412cad2Ssthen 			strlcat(diffargs, " -a", sizeof(diffargs));
85219c50abSray 			dflags |= D_FORCEASCII;
866412cad2Ssthen 			break;
876412cad2Ssthen 		case 'b':
886412cad2Ssthen 			strlcat(diffargs, " -b", sizeof(diffargs));
89219c50abSray 			dflags |= D_FOLDBLANKS;
906412cad2Ssthen 			break;
9108f90673Sjfb 		case 'c':
92f5638424Sjfb 			strlcat(diffargs, " -c", sizeof(diffargs));
93f9b67873Sniallo 			diff_format = D_CONTEXT;
9408f90673Sjfb 			break;
9572026f1aSnicm 		case 'C':
9672026f1aSnicm 			diff_context = strtonum(optarg, 0, INT_MAX, &errstr);
9772026f1aSnicm 			if (errstr != NULL)
9872026f1aSnicm 				fatal("context lines %s: %s", errstr, optarg);
9972026f1aSnicm 			strlcat(diffargs, " -C ", sizeof(diffargs));
10072026f1aSnicm 			strlcat(diffargs, optarg, sizeof(diffargs));
10172026f1aSnicm 			diff_format = D_CONTEXT;
10272026f1aSnicm 			break;
1036412cad2Ssthen 		case 'd':
1046412cad2Ssthen 			strlcat(diffargs, " -d", sizeof(diffargs));
105219c50abSray 			dflags |= D_MINIMAL;
1066412cad2Ssthen 			break;
107be756b91Stobias 		case 'D':
108be756b91Stobias 			if (date1 == -1 && rev1 == NULL) {
109*092db204Sray 				if ((date1 = date_parse(optarg)) == -1)
110*092db204Sray 					fatal("invalid date: %s", optarg);
111909bf3e0Stobias 				dateflag1 = optarg;
112be756b91Stobias 			} else if (date2 == -1 && rev2 == NULL) {
113*092db204Sray 				if ((date2 = date_parse(optarg)) == -1)
114*092db204Sray 					fatal("invalid date: %s", optarg);
115909bf3e0Stobias 				dateflag2 = optarg;
116be756b91Stobias 			} else {
117be756b91Stobias 				fatal("no more than 2 revisions/dates can"
118be756b91Stobias 				    " be specified");
119be756b91Stobias 			}
120be756b91Stobias 			break;
121fd660bf2Stobias 		case 'f':
122fd660bf2Stobias 			force_head = 1;
123fd660bf2Stobias 			break;
124fd660bf2Stobias 		case 'i':
125fd660bf2Stobias 			strlcat(diffargs, " -i", sizeof(diffargs));
126219c50abSray 			dflags |= D_IGNORECASE;
127fd660bf2Stobias 			break;
12837fdff3fStobias 		case 'k':
12937fdff3fStobias 			koptstr = optarg;
13037fdff3fStobias 			kflag = rcs_kflag_get(koptstr);
13137fdff3fStobias 			if (RCS_KWEXP_INVAL(kflag)) {
13237fdff3fStobias 				cvs_log(LP_ERR,
1330bc1d395Stobias 				    "invalid RCS keyword expansion mode");
134aeaaf4a6Stobias 				fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
135aeaaf4a6Stobias 				    cvs_cmd_diff.cmd_synopsis :
136aeaaf4a6Stobias 				    cvs_cmd_rdiff.cmd_synopsis);
13737fdff3fStobias 			}
13837fdff3fStobias 			break;
1391890abdaSjoris 		case 'l':
1401890abdaSjoris 			flags &= ~CR_RECURSE_DIRS;
1411890abdaSjoris 			break;
142394180a4Sjfb 		case 'n':
143394180a4Sjfb 			strlcat(diffargs, " -n", sizeof(diffargs));
144f9b67873Sniallo 			diff_format = D_RCSDIFF;
145394180a4Sjfb 			break;
14667caf486Sjoris 		case 'N':
147fd687d09Stobias 			strlcat(diffargs, " -N", sizeof(diffargs));
1482d6f447eSjoris 			Nflag = 1;
14967caf486Sjoris 			break;
150261cb0daSjoris 		case 'p':
151261cb0daSjoris 			strlcat(diffargs, " -p", sizeof(diffargs));
152219c50abSray 			dflags |= D_PROTOTYPE;
153261cb0daSjoris 			break;
154bcf22459Stobias 		case 'R':
155bcf22459Stobias 			flags |= CR_RECURSE_DIRS;
156bcf22459Stobias 			break;
15708f90673Sjfb 		case 'r':
158be756b91Stobias 			if (date1 == -1 && rev1 == NULL) {
1594230e8b3Sjoris 				rev1 = optarg;
160be756b91Stobias 			} else if (date2 == -1 && rev2 == NULL) {
1614230e8b3Sjoris 				rev2 = optarg;
16216cfc147Sjoris 			} else {
1633ad3fb45Sjoris 				fatal("no more than 2 revisions/dates can"
16408f90673Sjfb 				    " be specified");
16508f90673Sjfb 			}
16608f90673Sjfb 			break;
167219c50abSray 		case 't':
168219c50abSray 			strlcat(diffargs, " -t", sizeof(diffargs));
169219c50abSray 			dflags |= D_EXPANDTABS;
170219c50abSray 			break;
17108f90673Sjfb 		case 'u':
172f5638424Sjfb 			strlcat(diffargs, " -u", sizeof(diffargs));
173f9b67873Sniallo 			diff_format = D_UNIFIED;
17408f90673Sjfb 			break;
17572026f1aSnicm 		case 'U':
17672026f1aSnicm 			diff_context = strtonum(optarg, 0, INT_MAX, &errstr);
17772026f1aSnicm 			if (errstr != NULL)
17872026f1aSnicm 				fatal("context lines %s: %s", errstr, optarg);
17972026f1aSnicm 			strlcat(diffargs, " -U ", sizeof(diffargs));
18072026f1aSnicm 			strlcat(diffargs, optarg, sizeof(diffargs));
18172026f1aSnicm 			diff_format = D_UNIFIED;
18272026f1aSnicm 			break;
18337fdff3fStobias 		case 'V':
18437fdff3fStobias 			fatal("the -V option is obsolete "
18537fdff3fStobias 			    "and should not be used");
1866412cad2Ssthen 		case 'w':
1876412cad2Ssthen 			strlcat(diffargs, " -w", sizeof(diffargs));
188219c50abSray 			dflags |= D_IGNOREBLANKS;
1896412cad2Ssthen 			break;
19008f90673Sjfb 		default:
191aeaaf4a6Stobias 			fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
192aeaaf4a6Stobias 			    cvs_cmd_diff.cmd_synopsis :
193aeaaf4a6Stobias 			    cvs_cmd_rdiff.cmd_synopsis);
19408f90673Sjfb 		}
19508f90673Sjfb 	}
19608f90673Sjfb 
1973ad3fb45Sjoris 	argc -= optind;
1983ad3fb45Sjoris 	argv += optind;
199dc6a6879Sjfb 
2003ad3fb45Sjoris 	cr.enterdir = NULL;
2013ad3fb45Sjoris 	cr.leavedir = NULL;
2029fac60a5Sjoris 
203fd660bf2Stobias 	if (cvs_cmdop == CVS_OP_RDIFF) {
204909bf3e0Stobias 		if (rev1 == NULL && rev2 == NULL && dateflag1 == NULL &&
205909bf3e0Stobias 		    dateflag2 == NULL)
206fd660bf2Stobias 			fatal("must specify at least one revision/date!");
207fd660bf2Stobias 
208fd687d09Stobias 		if (!argc)
209fd687d09Stobias 			fatal("%s", cvs_cmd_rdiff.cmd_synopsis);
210fd687d09Stobias 
211fd660bf2Stobias 		if (!diff_format) {
212fd660bf2Stobias 			strlcat(diffargs, " -c", sizeof(diffargs));
213fd660bf2Stobias 			diff_format = D_CONTEXT;
214fd660bf2Stobias 		}
215fd660bf2Stobias 
216fd660bf2Stobias 		flags |= CR_REPO;
217fd660bf2Stobias 	}
218fd660bf2Stobias 
2199fac60a5Sjoris 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
22080f6ca9bSjoris 		cvs_client_connect_to_server();
2219fac60a5Sjoris 		cr.fileproc = cvs_client_sendfile;
2229fac60a5Sjoris 
2239fac60a5Sjoris 		if (!(flags & CR_RECURSE_DIRS))
2249fac60a5Sjoris 			cvs_client_send_request("Argument -l");
2259fac60a5Sjoris 
22637fdff3fStobias 		if (kflag)
22737fdff3fStobias 			cvs_client_send_request("Argument -k%s", koptstr);
22837fdff3fStobias 
2299fac60a5Sjoris 		switch (diff_format) {
2309fac60a5Sjoris 		case D_CONTEXT:
23172026f1aSnicm 			if (cvs_cmdop == CVS_OP_RDIFF)
2329fac60a5Sjoris 				cvs_client_send_request("Argument -c");
23372026f1aSnicm 			else {
23472026f1aSnicm 				cvs_client_send_request("Argument -C %d",
23572026f1aSnicm 				    diff_context);
23672026f1aSnicm 			}
2379fac60a5Sjoris 			break;
2389fac60a5Sjoris 		case D_RCSDIFF:
2399fac60a5Sjoris 			cvs_client_send_request("Argument -n");
2409fac60a5Sjoris 			break;
2419fac60a5Sjoris 		case D_UNIFIED:
24272026f1aSnicm 			if (cvs_cmdop == CVS_OP_RDIFF)
2439fac60a5Sjoris 				cvs_client_send_request("Argument -u");
24472026f1aSnicm 			else {
24572026f1aSnicm 				cvs_client_send_request("Argument -U %d",
24672026f1aSnicm 				    diff_context);
24772026f1aSnicm 			}
2489fac60a5Sjoris 			break;
2499fac60a5Sjoris 		default:
2509fac60a5Sjoris 			break;
2519fac60a5Sjoris 		}
2529fac60a5Sjoris 
2539fac60a5Sjoris 		if (Nflag == 1)
2549fac60a5Sjoris 			cvs_client_send_request("Argument -N");
2559fac60a5Sjoris 
256219c50abSray 		if (dflags & D_PROTOTYPE)
2579fac60a5Sjoris 			cvs_client_send_request("Argument -p");
2589fac60a5Sjoris 
2599fac60a5Sjoris 		if (rev1 != NULL)
2609fac60a5Sjoris 			cvs_client_send_request("Argument -r%s", rev1);
2619fac60a5Sjoris 		if (rev2 != NULL)
2629fac60a5Sjoris 			cvs_client_send_request("Argument -r%s", rev2);
263909bf3e0Stobias 
264909bf3e0Stobias 		if (dateflag1 != NULL)
265909bf3e0Stobias 			cvs_client_send_request("Argument -D%s", dateflag1);
266909bf3e0Stobias 		if (dateflag2 != NULL)
267909bf3e0Stobias 			cvs_client_send_request("Argument -D%s", dateflag2);
2689fac60a5Sjoris 	} else {
269fd660bf2Stobias 		if (cvs_cmdop == CVS_OP_RDIFF &&
270fd660bf2Stobias 		    chdir(current_cvsroot->cr_dir) == -1)
271fd660bf2Stobias 			fatal("cvs_diff: %s", strerror(errno));
272fd660bf2Stobias 
273bc5d89feSjoris 		cr.fileproc = cvs_diff_local;
2749fac60a5Sjoris 	}
2759fac60a5Sjoris 
2761890abdaSjoris 	cr.flags = flags;
277dc6a6879Sjfb 
2784230e8b3Sjoris 	diff_rev1 = diff_rev2 = NULL;
2794230e8b3Sjoris 
280fd660bf2Stobias 	if (cvs_cmdop == CVS_OP_DIFF ||
281fd660bf2Stobias 	    current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
2823ad3fb45Sjoris 		if (argc > 0)
2833ad3fb45Sjoris 			cvs_file_run(argc, argv, &cr);
284d990145dSray 		else
2853ad3fb45Sjoris 			cvs_file_run(1, &arg, &cr);
286fd660bf2Stobias 	}
287e4276007Sjfb 
2889fac60a5Sjoris 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
2899fac60a5Sjoris 		cvs_client_send_files(argv, argc);
2909fac60a5Sjoris 		cvs_client_senddir(".");
291fd660bf2Stobias 
292fd660bf2Stobias 		cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ?
293fd660bf2Stobias 		    "rdiff" : "diff");
294fd660bf2Stobias 
2959fac60a5Sjoris 		cvs_client_get_responses();
2969fac60a5Sjoris 	}
2979fac60a5Sjoris 
298e4276007Sjfb 	return (0);
299e4276007Sjfb }
300e4276007Sjfb 
30101af718aSjoris void
3023ad3fb45Sjoris cvs_diff_local(struct cvs_file *cf)
303f9b67873Sniallo {
304741443cbSjoris 	BUF *b1;
3052e0d696aSjoris 	int fd1, fd2;
3063ad3fb45Sjoris 	struct stat st;
3073ad3fb45Sjoris 	struct timeval tv[2], tv2[2];
3086534056aStobias 	struct tm datetm;
309be756b91Stobias 	char rbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ], *p1, *p2;
310d71a0c9bSxsa 
311741443cbSjoris 	b1 = NULL;
312fd687d09Stobias 	fd1 = fd2 = -1;
313fd687d09Stobias 	p1 = p2 = NULL;
314f9b67873Sniallo 
3153ad3fb45Sjoris 	cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path);
3163ad3fb45Sjoris 
3173ad3fb45Sjoris 	if (cf->file_type == CVS_DIR) {
3183ad3fb45Sjoris 		if (verbosity > 1)
3191489d205Sjoris 			cvs_log(LP_ERR, "Diffing inside %s", cf->file_path);
3203ad3fb45Sjoris 		return;
3213ad3fb45Sjoris 	}
3223ad3fb45Sjoris 
32351ef6581Sjoris 	cvs_file_classify(cf, cvs_directory_tag);
3243ad3fb45Sjoris 
325fd687d09Stobias 	if (cvs_cmdop == CVS_OP_DIFF) {
326fd687d09Stobias 		if (cf->file_ent == NULL) {
327fd687d09Stobias 			cvs_log(LP_ERR, "I know nothing about %s",
32867caf486Sjoris 			    cf->file_path);
3293ad3fb45Sjoris 			return;
33067caf486Sjoris 		}
3313ad3fb45Sjoris 
332fd687d09Stobias 		switch (cf->file_ent->ce_status) {
333fd687d09Stobias 		case CVS_ENT_ADDED:
334fd687d09Stobias 			if (Nflag == 0) {
335fd687d09Stobias 				cvs_log(LP_ERR, "%s is a new entry, no "
336fd687d09Stobias 				    "comparison available", cf->file_path);
337fd687d09Stobias 				return;
338fd687d09Stobias 			}
3395cf15c45Sjoris 			if (!(cf->file_flags & FILE_ON_DISK)) {
340fd687d09Stobias 				cvs_log(LP_ERR, "cannot find %s",
341fd687d09Stobias 				    cf->file_path);
342fd687d09Stobias 				return;
343fd687d09Stobias 			}
344fd687d09Stobias 			break;
345fd687d09Stobias 		case CVS_ENT_REMOVED:
346fd687d09Stobias 			if (Nflag == 0) {
347fd687d09Stobias 				cvs_log(LP_ERR, "%s was removed, no "
348fd687d09Stobias 				    "comparison available", cf->file_path);
349fd687d09Stobias 				return;
350fd687d09Stobias 			}
351fd687d09Stobias 			if (cf->file_rcs == NULL) {
352fd687d09Stobias 				cvs_log(LP_ERR, "cannot find RCS file for %s",
353fd687d09Stobias 				    cf->file_path);
354fd687d09Stobias 				return;
355fd687d09Stobias 			}
356fd687d09Stobias 			break;
357fd687d09Stobias 		default:
3585cf15c45Sjoris 			if (!(cf->file_flags & FILE_ON_DISK)) {
3591489d205Sjoris 				cvs_printf("? %s\n", cf->file_path);
360fd687d09Stobias 				return;
361fd687d09Stobias 			}
362c638a04bSjoris 
363fd687d09Stobias 			if (cf->file_rcs == NULL) {
364fd687d09Stobias 				cvs_log(LP_ERR, "cannot find RCS file for %s",
365fd687d09Stobias 				    cf->file_path);
366fd687d09Stobias 				return;
367fd687d09Stobias 			}
368fd687d09Stobias 			break;
369fd687d09Stobias 		}
370fd687d09Stobias 	}
371fd687d09Stobias 
372be756b91Stobias 	if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL &&
373be756b91Stobias 	    date1 == -1 && date2 == -1)
374fd687d09Stobias 		return;
375fd687d09Stobias 
376ebd92267Sjoris 	if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) {
377e28eda4eStobias 		cvs_log(LP_ERR, "no head revision in RCS file for %s\n",
378e28eda4eStobias 		    cf->file_path);
379e28eda4eStobias 		return;
380e28eda4eStobias 	}
381e28eda4eStobias 
382fd687d09Stobias 	if (kflag && cf->file_rcs != NULL)
38337fdff3fStobias 		rcs_kwexp_set(cf->file_rcs, kflag);
38437fdff3fStobias 
385fd687d09Stobias 	if (cf->file_rcs == NULL)
386fd687d09Stobias 		diff_rev1 = NULL;
387be756b91Stobias 	else if (rev1 != NULL || date1 != -1) {
388be756b91Stobias 		cvs_specified_date = date1;
389fd687d09Stobias 		diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs);
390fd687d09Stobias 		if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) {
391223285fdStobias 			if (rev1 != NULL) {
392fd687d09Stobias 				cvs_log(LP_ERR, "tag %s not in file %s", rev1,
393fd687d09Stobias 				    cf->file_path);
394223285fdStobias 				goto cleanup;
395223285fdStobias 			} else if (Nflag) {
396223285fdStobias 				diff_rev1 = NULL;
397223285fdStobias 			} else {
3986534056aStobias 				gmtime_r(&cvs_specified_date, &datetm);
399be756b91Stobias 				strftime(tbuf, sizeof(tbuf),
4006534056aStobias 				    "%Y.%m.%d.%H.%M.%S", &datetm);
401be756b91Stobias 				cvs_log(LP_ERR, "no revision for date %s in "
402be756b91Stobias 				    "file %s", tbuf, cf->file_path);
403fd687d09Stobias 				goto cleanup;
404223285fdStobias 			}
405fd687d09Stobias 		} else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
406fd687d09Stobias 		    force_head) {
407fd660bf2Stobias 			/* -f is not allowed for unknown symbols */
408fd687d09Stobias 			if ((diff_rev1 = rcsnum_parse(rev1)) == NULL)
409fd660bf2Stobias 				fatal("no such tag %s", rev1);
410fd660bf2Stobias 			rcsnum_free(diff_rev1);
411fd660bf2Stobias 
412fd687d09Stobias 			diff_rev1 = cf->file_rcs->rf_head;
413fd660bf2Stobias 		}
414be756b91Stobias 		cvs_specified_date = -1;
415fd687d09Stobias 	} else if (cvs_cmdop == CVS_OP_DIFF) {
416fd687d09Stobias 		if (cf->file_ent->ce_status == CVS_ENT_ADDED)
417fd687d09Stobias 			diff_rev1 = NULL;
418fd687d09Stobias 		else
419fd687d09Stobias 			diff_rev1 = cf->file_ent->ce_rev;
420fd660bf2Stobias 	}
42151ef6581Sjoris 
422fd687d09Stobias 	if (cf->file_rcs == NULL)
423fd687d09Stobias 		diff_rev2 = NULL;
424be756b91Stobias 	else if (rev2 != NULL || date2 != -1) {
425be756b91Stobias 		cvs_specified_date = date2;
426fd687d09Stobias 		diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs);
427fd687d09Stobias 		if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) {
428be756b91Stobias 			if (rev2 != NULL) {
429fd687d09Stobias 				cvs_log(LP_ERR, "tag %s not in file %s", rev2,
430fd687d09Stobias 				    cf->file_path);
431223285fdStobias 				goto cleanup;
432223285fdStobias 			} else if (Nflag) {
433223285fdStobias 				diff_rev2 = NULL;
434be756b91Stobias 			} else {
4356534056aStobias 				gmtime_r(&cvs_specified_date, &datetm);
436be756b91Stobias 				strftime(tbuf, sizeof(tbuf),
4376534056aStobias 				    "%Y.%m.%d.%H.%M.%S", &datetm);
438be756b91Stobias 				cvs_log(LP_ERR, "no revision for date %s in "
439be756b91Stobias 				    "file %s", tbuf, cf->file_path);
440fd687d09Stobias 				goto cleanup;
441223285fdStobias 			}
442fd687d09Stobias 		} else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
443fd687d09Stobias 		    force_head) {
444fd660bf2Stobias 			/* -f is not allowed for unknown symbols */
445fd687d09Stobias 			if ((diff_rev2 = rcsnum_parse(rev2)) == NULL)
446fd660bf2Stobias 				fatal("no such tag %s", rev2);
447fd660bf2Stobias 			rcsnum_free(diff_rev2);
448fd660bf2Stobias 
449fd687d09Stobias 			diff_rev2 = cf->file_rcs->rf_head;
450fd660bf2Stobias 		}
451be756b91Stobias 		cvs_specified_date = -1;
452fd687d09Stobias 	} else if (cvs_cmdop == CVS_OP_RDIFF)
453fd687d09Stobias 		diff_rev2 = cf->file_rcs->rf_head;
454fd687d09Stobias 	else if (cf->file_ent->ce_status == CVS_ENT_REMOVED)
455fd687d09Stobias 		diff_rev2 = NULL;
456fd687d09Stobias 
457fd687d09Stobias 	if (diff_rev1 != NULL && diff_rev2 != NULL &&
458fd687d09Stobias 	    rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0)
459fd687d09Stobias 		goto cleanup;
460fd687d09Stobias 
461fd687d09Stobias 	switch (cvs_cmdop) {
462fd687d09Stobias 	case CVS_OP_DIFF:
46352a66329Sjoris 		if (cf->file_status == FILE_UPTODATE) {
46452a66329Sjoris 			if (diff_rev2 == NULL &&
46552a66329Sjoris 			    !rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0))
46652a66329Sjoris 				goto cleanup;
46752a66329Sjoris 		}
468fd687d09Stobias 		break;
469fd687d09Stobias 	case CVS_OP_RDIFF:
470fd687d09Stobias 		if (diff_rev1 == NULL && diff_rev2 == NULL)
471fd687d09Stobias 			goto cleanup;
472fd687d09Stobias 		break;
473fd660bf2Stobias 	}
474fd660bf2Stobias 
475fd660bf2Stobias 	cvs_printf("Index: %s\n", cf->file_path);
476fd660bf2Stobias 	if (cvs_cmdop == CVS_OP_DIFF)
477fd687d09Stobias 		cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV,
478fd687d09Stobias 		    cf->file_rcs != NULL ? cf->file_rpath : cf->file_path);
479fd660bf2Stobias 
480fd660bf2Stobias 	if (diff_rev1 != NULL) {
481fd687d09Stobias 		if (cvs_cmdop == CVS_OP_DIFF && diff_rev1 != NULL) {
482fd687d09Stobias 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
483fd687d09Stobias 			cvs_printf("retrieving revision %s\n", rbuf);
484fd687d09Stobias 		}
4853ad3fb45Sjoris 
486fd687d09Stobias 		tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev1);
4873ad3fb45Sjoris 		tv[0].tv_usec = 0;
4883ad3fb45Sjoris 		tv[1] = tv[0];
489741443cbSjoris 
490fd687d09Stobias 		(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
491fd687d09Stobias 		fd1 = rcs_rev_write_stmp(cf->file_rcs, diff_rev1, p1, 0);
4922e0d696aSjoris 		if (futimes(fd1, tv) == -1)
493fd660bf2Stobias 			fatal("cvs_diff_local: utimes failed");
494fd660bf2Stobias 	}
4953ad3fb45Sjoris 
496fd687d09Stobias 	if (diff_rev2 != NULL) {
497fd687d09Stobias 		if (cvs_cmdop == CVS_OP_DIFF && rev2 != NULL) {
498fd660bf2Stobias 			(void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf));
499fd687d09Stobias 			cvs_printf("retrieving revision %s\n", rbuf);
500fd687d09Stobias 		}
5013ad3fb45Sjoris 
5023ad3fb45Sjoris 		tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2);
5033ad3fb45Sjoris 		tv2[0].tv_usec = 0;
5043ad3fb45Sjoris 		tv2[1] = tv2[0];
505741443cbSjoris 
506fd687d09Stobias 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
5072e0d696aSjoris 		fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0);
5082e0d696aSjoris 		if (futimes(fd2, tv2) == -1)
509fd660bf2Stobias 			fatal("cvs_diff_local: utimes failed");
5105cf15c45Sjoris 	} else if (cvs_cmdop == CVS_OP_DIFF &&
5115cf15c45Sjoris 	    (cf->file_flags & FILE_ON_DISK) &&
512fd687d09Stobias 	    cf->file_ent->ce_status != CVS_ENT_REMOVED) {
513e91b8a58Sjoris 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
514e91b8a58Sjoris 		if (cvs_server_active == 1 && cf->fd == -1) {
515e91b8a58Sjoris 			tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs,
516e91b8a58Sjoris 			    cf->file_ent->ce_rev);
517e91b8a58Sjoris 			tv2[0].tv_usec = 0;
518e91b8a58Sjoris 			tv2[1] = tv2[0];
519e91b8a58Sjoris 
520e91b8a58Sjoris 			fd2 = rcs_rev_write_stmp(cf->file_rcs,
521e91b8a58Sjoris 			    cf->file_ent->ce_rev, p2, 0);
522e91b8a58Sjoris 			if (futimes(fd2, tv2) == -1)
523e91b8a58Sjoris 				fatal("cvs_diff_local: futimes failed");
524e91b8a58Sjoris 		} else {
5253ad3fb45Sjoris 			if (fstat(cf->fd, &st) == -1)
5263ad3fb45Sjoris 				fatal("fstat failed %s", strerror(errno));
5277bb3ddb0Sray 			b1 = buf_load_fd(cf->fd);
5283ad3fb45Sjoris 
5293ad3fb45Sjoris 			tv2[0].tv_sec = st.st_mtime;
5303ad3fb45Sjoris 			tv2[0].tv_usec = 0;
5313ad3fb45Sjoris 			tv2[1] = tv2[0];
532741443cbSjoris 
5337bb3ddb0Sray 			fd2 = buf_write_stmp(b1, p2, tv2);
5347bb3ddb0Sray 			buf_free(b1);
5353ad3fb45Sjoris 		}
536e91b8a58Sjoris 	}
5373ad3fb45Sjoris 
538fd687d09Stobias 	switch (cvs_cmdop) {
539fd687d09Stobias 	case CVS_OP_DIFF:
5403ad3fb45Sjoris 		cvs_printf("%s", diffargs);
5413ad3fb45Sjoris 
542fd687d09Stobias 		if (rev1 != NULL && diff_rev1 != NULL) {
543fd687d09Stobias 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
5443ad3fb45Sjoris 			cvs_printf(" -r%s", rbuf);
5453ad3fb45Sjoris 
546fd687d09Stobias 			if (rev2 != NULL && diff_rev2 != NULL) {
547fd660bf2Stobias 				(void)rcsnum_tostr(diff_rev2, rbuf,
548fd660bf2Stobias 				    sizeof(rbuf));
5493ad3fb45Sjoris 				cvs_printf(" -r%s", rbuf);
5503ad3fb45Sjoris 			}
55167caf486Sjoris 		}
5523ad3fb45Sjoris 
553fd660bf2Stobias 		if (diff_rev2 == NULL)
554fd687d09Stobias 			cvs_printf(" %s", cf->file_path);
55509d59ca3Stobias 		cvs_printf("\n");
556fd687d09Stobias 		break;
557fd687d09Stobias 	case CVS_OP_RDIFF:
558fd660bf2Stobias 		cvs_printf("diff ");
559fd660bf2Stobias 		switch (diff_format) {
560fd660bf2Stobias 		case D_CONTEXT:
561fd660bf2Stobias 			cvs_printf("-c ");
562fd660bf2Stobias 			break;
563fd660bf2Stobias 		case D_RCSDIFF:
564fd660bf2Stobias 			cvs_printf("-n ");
565fd660bf2Stobias 			break;
566fd660bf2Stobias 		case D_UNIFIED:
567fd660bf2Stobias 			cvs_printf("-u ");
568fd660bf2Stobias 			break;
569fd660bf2Stobias 		default:
570fd660bf2Stobias 			break;
571fd660bf2Stobias 		}
572fd660bf2Stobias 		if (diff_rev1 == NULL) {
573fd660bf2Stobias 			cvs_printf("%s ", CVS_PATH_DEVNULL);
574fd660bf2Stobias 		} else {
575fd660bf2Stobias 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
576fd660bf2Stobias 			cvs_printf("%s:%s ", cf->file_path, rbuf);
577fd660bf2Stobias 		}
5783ad3fb45Sjoris 
579fd660bf2Stobias 		if (diff_rev2 == NULL) {
580fd660bf2Stobias 			cvs_printf("%s:removed\n", cf->file_path);
581fd660bf2Stobias 		} else {
582fd660bf2Stobias 			(void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 :
583fd660bf2Stobias 			    cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
584fd660bf2Stobias 			cvs_printf("%s:%s\n", cf->file_path, rbuf);
585fd660bf2Stobias 		}
586fd687d09Stobias 		break;
587fd660bf2Stobias 	}
588fd660bf2Stobias 
589fd687d09Stobias 	if (fd1 == -1) {
590fd687d09Stobias 		if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
591fd687d09Stobias 			fatal("cannot open %s", CVS_PATH_DEVNULL);
592fd687d09Stobias 	}
593fd687d09Stobias 	if (fd2 == -1) {
594fd687d09Stobias 		if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
595fd687d09Stobias 			fatal("cannot open %s", CVS_PATH_DEVNULL);
596741443cbSjoris 	}
5973ad3fb45Sjoris 
59857003866Sray 	if (diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL,
599219c50abSray 	    p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL,
600219c50abSray 	    dflags) == D_ERROR)
601d71a0c9bSxsa 		fatal("cvs_diff_local: failed to get RCS patch");
602d71a0c9bSxsa 
6032e0d696aSjoris 	close(fd1);
6042e0d696aSjoris 	close(fd2);
6052e0d696aSjoris 
6067a9e6d11Sray 	worklist_run(&temp_files, worklist_unlink);
60767caf486Sjoris 
608d71a0c9bSxsa 	if (p1 != NULL)
609d71a0c9bSxsa 		xfree(p1);
610d71a0c9bSxsa 	if (p2 != NULL)
611d71a0c9bSxsa 		xfree(p2);
612d71a0c9bSxsa 
613fd687d09Stobias cleanup:
614fd687d09Stobias 	if (diff_rev1 != NULL &&
615fd687d09Stobias 	    (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) &&
616fd687d09Stobias 	    (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev))
617fd687d09Stobias 		xfree(diff_rev1);
618fd687d09Stobias 	diff_rev1 = NULL;
619261cb0daSjoris 
620fd687d09Stobias 	if (diff_rev2 != NULL &&
621fd687d09Stobias 	    (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head))
622fd687d09Stobias 		xfree(diff_rev2);
623fd687d09Stobias 	diff_rev2 = NULL;
624f9b67873Sniallo }
625