xref: /openbsd-src/usr.bin/cvs/diff.c (revision 66ad965f4873a0970dea06fb53c307b8385e5a94)
1 /*	$OpenBSD: diff.c,v 1.134 2008/03/13 19:54:34 sthen Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/stat.h>
19 #include <sys/time.h>
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "cvs.h"
28 #include "diff.h"
29 #include "remote.h"
30 
31 void	cvs_diff_local(struct cvs_file *);
32 
33 static int	 Nflag = 0;
34 static int	 force_head = 0;
35 static char	*koptstr;
36 static char	*rev1 = NULL;
37 static char	*rev2 = NULL;
38 
39 struct cvs_cmd cvs_cmd_diff = {
40 	CVS_OP_DIFF, CVS_USE_WDIR, "diff",
41 	{ "di", "dif" },
42 	"Show differences between revisions",
43 	"[-cilNnpRu] [[-D date] [-r rev] [-D date2 | -r rev2]] "
44 	"[-k mode] [file ...]",
45 	"cfD:ik:lNnpr:Ru",
46 	NULL,
47 	cvs_diff
48 };
49 
50 struct cvs_cmd cvs_cmd_rdiff = {
51 	CVS_OP_RDIFF, 0, "rdiff",
52 	{ "patch", "pa" },
53 	"Show differences between revisions",
54 	"[-flR] [-c | -u] [-s | -t] [-V ver] -D date | -r rev\n"
55 	"[-D date2 | -r rev2] [-k mode] module ...",
56 	"cfD:k:lr:RuV:",
57 	NULL,
58 	cvs_diff
59 };
60 
61 int
62 cvs_diff(int argc, char **argv)
63 {
64 	int ch, flags;
65 	char *arg = ".";
66 	struct cvs_recursion cr;
67 
68 	flags = CR_RECURSE_DIRS;
69 	strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff",
70 	    sizeof(diffargs));
71 
72 	while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ?
73 	    cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) {
74 		switch (ch) {
75 		case 'c':
76 			strlcat(diffargs, " -c", sizeof(diffargs));
77 			diff_format = D_CONTEXT;
78 			break;
79 		case 'f':
80 			force_head = 1;
81 			break;
82 		case 'i':
83 			strlcat(diffargs, " -i", sizeof(diffargs));
84 			diff_iflag = 1;
85 			break;
86 		case 'k':
87 			koptstr = optarg;
88 			kflag = rcs_kflag_get(koptstr);
89 			if (RCS_KWEXP_INVAL(kflag)) {
90 				cvs_log(LP_ERR,
91 				    "invalid RCS keyword expension mode");
92 				fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
93 				    cvs_cmd_diff.cmd_synopsis :
94 				    cvs_cmd_rdiff.cmd_synopsis);
95 			}
96 			break;
97 		case 'l':
98 			flags &= ~CR_RECURSE_DIRS;
99 			break;
100 		case 'n':
101 			strlcat(diffargs, " -n", sizeof(diffargs));
102 			diff_format = D_RCSDIFF;
103 			break;
104 		case 'N':
105 			Nflag = 1;
106 			break;
107 		case 'p':
108 			strlcat(diffargs, " -p", sizeof(diffargs));
109 			diff_pflag = 1;
110 			break;
111 		case 'R':
112 			flags |= CR_RECURSE_DIRS;
113 			break;
114 		case 'r':
115 			if (rev1 == NULL) {
116 				rev1 = optarg;
117 			} else if (rev2 == NULL) {
118 				rev2 = optarg;
119 			} else {
120 				fatal("no more than 2 revisions/dates can"
121 				    " be specified");
122 			}
123 			break;
124 		case 'u':
125 			strlcat(diffargs, " -u", sizeof(diffargs));
126 			diff_format = D_UNIFIED;
127 			break;
128 		case 'V':
129 			fatal("the -V option is obsolete "
130 			    "and should not be used");
131 		default:
132 			fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
133 			    cvs_cmd_diff.cmd_synopsis :
134 			    cvs_cmd_rdiff.cmd_synopsis);
135 		}
136 	}
137 
138 	argc -= optind;
139 	argv += optind;
140 
141 	cr.enterdir = NULL;
142 	cr.leavedir = NULL;
143 
144 	if (cvs_cmdop == CVS_OP_RDIFF) {
145 		if (rev1 == NULL)
146 			fatal("must specify at least one revision/date!");
147 
148 		if (!diff_format) {
149 			strlcat(diffargs, " -c", sizeof(diffargs));
150 			diff_format = D_CONTEXT;
151 		}
152 
153 		flags |= CR_REPO;
154 	}
155 
156 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
157 		cvs_client_connect_to_server();
158 		cr.fileproc = cvs_client_sendfile;
159 
160 		if (!(flags & CR_RECURSE_DIRS))
161 			cvs_client_send_request("Argument -l");
162 
163 		if (kflag)
164 			cvs_client_send_request("Argument -k%s", koptstr);
165 
166 		switch (diff_format) {
167 		case D_CONTEXT:
168 			cvs_client_send_request("Argument -c");
169 			break;
170 		case D_RCSDIFF:
171 			cvs_client_send_request("Argument -n");
172 			break;
173 		case D_UNIFIED:
174 			cvs_client_send_request("Argument -u");
175 			break;
176 		default:
177 			break;
178 		}
179 
180 		if (Nflag == 1)
181 			cvs_client_send_request("Argument -N");
182 
183 		if (diff_pflag == 1)
184 			cvs_client_send_request("Argument -p");
185 
186 		if (rev1 != NULL)
187 			cvs_client_send_request("Argument -r%s", rev1);
188 		if (rev2 != NULL)
189 			cvs_client_send_request("Argument -r%s", rev2);
190 	} else {
191 		if (cvs_cmdop == CVS_OP_RDIFF &&
192 		    chdir(current_cvsroot->cr_dir) == -1)
193 			fatal("cvs_diff: %s", strerror(errno));
194 
195 		cr.fileproc = cvs_diff_local;
196 	}
197 
198 	cr.flags = flags;
199 
200 	diff_rev1 = diff_rev2 = NULL;
201 
202 	if (cvs_cmdop == CVS_OP_DIFF ||
203 	    current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
204 		if (argc > 0)
205 			cvs_file_run(argc, argv, &cr);
206 		else
207 			cvs_file_run(1, &arg, &cr);
208 	}
209 
210 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
211 		cvs_client_send_files(argv, argc);
212 		cvs_client_senddir(".");
213 
214 		cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ?
215 		    "rdiff" : "diff");
216 
217 		cvs_client_get_responses();
218 	}
219 
220 	return (0);
221 }
222 
223 void
224 cvs_diff_local(struct cvs_file *cf)
225 {
226 	RCSNUM *r1;
227 	BUF *b1;
228 	int fd1, fd2;
229 	struct stat st;
230 	struct timeval tv[2], tv2[2];
231 	char rbuf[CVS_REV_BUFSZ], *p1, *p2;
232 
233 	r1 = NULL;
234 	b1 = NULL;
235 
236 	cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path);
237 
238 	if (cf->file_type == CVS_DIR) {
239 		if (verbosity > 1)
240 			cvs_log(LP_NOTICE, "Diffing inside %s", cf->file_path);
241 		return;
242 	}
243 
244 	cvs_file_classify(cf, cvs_directory_tag);
245 
246 	if (cf->file_status == FILE_LOST) {
247 		cvs_log(LP_ERR, "cannot find file %s", cf->file_path);
248 		return;
249 	} else if (cf->file_status == FILE_UNKNOWN) {
250 		cvs_log(LP_ERR, "I know nothing about %s", cf->file_path);
251 		return;
252 	} else if (cf->file_status == FILE_ADDED && Nflag == 0) {
253 		cvs_log(LP_ERR, "%s is a new entry, no comparison available",
254 		    cf->file_path);
255 		return;
256 	} else if (cf->file_status == FILE_REMOVED && Nflag == 0) {
257 		cvs_log(LP_ERR, "%s was removed, no comparison available",
258 		    cf->file_path);
259 		return;
260 	} else if (cf->file_status == FILE_UPTODATE && rev1 == NULL &&
261 	     rev2 == NULL) {
262 		return;
263 	}
264 
265 	if (kflag)
266 		rcs_kwexp_set(cf->file_rcs, kflag);
267 
268 	if (rev1 != NULL)
269 		if ((diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs)) ==
270 		    NULL) {
271 			if (cvs_cmdop == CVS_OP_DIFF) {
272 				cvs_log(LP_ERR, "tag %s is not in file %s",
273 				    rev1, cf->file_path);
274 				return;
275 			}
276 			if (force_head) {
277 				/* -f is not allowed for unknown symbols */
278 				diff_rev1 = rcsnum_parse(rev1);
279 				if (diff_rev1 == NULL)
280 					fatal("no such tag %s", rev1);
281 				rcsnum_free(diff_rev1);
282 
283 				diff_rev1 = rcsnum_alloc();
284 				rcsnum_cpy(cf->file_rcs->rf_head, diff_rev1, 0);
285 			}
286 		}
287 
288 	if (rev2 != NULL)
289 		if ((diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs)) ==
290 		    NULL) {
291 			if (cvs_cmdop == CVS_OP_DIFF) {
292 				rcsnum_free(diff_rev1);
293 				cvs_log(LP_ERR, "tag %s is not in file %s",
294 				    rev2, cf->file_path);
295 				return;
296 			}
297 			if (force_head) {
298 				/* -f is not allowed for unknown symbols */
299 				diff_rev2 = rcsnum_parse(rev2);
300 				if (diff_rev2 == NULL)
301 					fatal("no such tag %s", rev2);
302 				rcsnum_free(diff_rev2);
303 
304 				diff_rev2 = rcsnum_alloc();
305 				rcsnum_cpy(cf->file_rcs->rf_head, diff_rev2, 0);
306 			}
307 		}
308 
309 	if (cvs_cmdop == CVS_OP_RDIFF && diff_rev1 == NULL && diff_rev2 == NULL)
310 		return;
311 
312 	diff_file = cf->file_path;
313 
314 	cvs_printf("Index: %s\n", cf->file_path);
315 	if (cvs_cmdop == CVS_OP_DIFF)
316 		cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV, cf->file_rpath);
317 
318 	(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
319 	(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
320 
321 	if (cf->file_status != FILE_ADDED) {
322 		if (diff_rev1 != NULL)
323 			r1 = diff_rev1;
324 		else if (cf->file_ent != NULL)
325 			r1 = cf->file_ent->ce_rev;
326 		else
327 			r1 = NULL;
328 
329 		diff_rev1 = r1;
330 
331 		if (diff_rev1 != NULL) {
332 			(void)rcsnum_tostr(r1, rbuf, sizeof(rbuf));
333 
334 			tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, r1);
335 			tv[0].tv_usec = 0;
336 			tv[1] = tv[0];
337 
338 			if (cvs_cmdop == CVS_OP_DIFF)
339 				cvs_printf("retrieving revision %s\n", rbuf);
340 			fd1 = rcs_rev_write_stmp(cf->file_rcs, r1, p1, 0);
341 			if (futimes(fd1, tv) == -1)
342 				fatal("cvs_diff_local: utimes failed");
343 		}
344 	}
345 
346 	if (diff_rev2 != NULL && cf->file_status != FILE_ADDED &&
347 	    cf->file_status != FILE_REMOVED) {
348 		(void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf));
349 
350 		tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2);
351 		tv2[0].tv_usec = 0;
352 		tv2[1] = tv2[0];
353 
354 		if (cvs_cmdop == CVS_OP_DIFF)
355 			cvs_printf("retrieving revision %s\n", rbuf);
356 		fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0);
357 		if (futimes(fd2, tv2) == -1)
358 			fatal("cvs_diff_local: utimes failed");
359 	} else if (cf->file_status != FILE_REMOVED) {
360 		if (cvs_cmdop == CVS_OP_RDIFF || (cvs_server_active == 1 &&
361 		    cf->file_status != FILE_MODIFIED)) {
362 			if (diff_rev2 != NULL) {
363 				tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs,
364 				    cf->file_rcsrev);
365 				tv2[0].tv_usec = 0;
366 				tv2[1] = tv2[0];
367 
368 				fd2 = rcs_rev_write_stmp(cf->file_rcs,
369 				    cf->file_rcsrev, p2, 0);
370 				if (futimes(fd2, tv2) == -1)
371 					fatal("cvs_diff_local: utimes failed");
372 			}
373 		} else {
374 			if (fstat(cf->fd, &st) == -1)
375 				fatal("fstat failed %s", strerror(errno));
376 			b1 = cvs_buf_load_fd(cf->fd);
377 
378 			tv2[0].tv_sec = st.st_mtime;
379 			tv2[0].tv_usec = 0;
380 			tv2[1] = tv2[0];
381 
382 			fd2 = cvs_buf_write_stmp(b1, p2, tv2);
383 			cvs_buf_free(b1);
384 		}
385 	}
386 
387 	if (cvs_cmdop == CVS_OP_DIFF) {
388 		cvs_printf("%s", diffargs);
389 
390 		if (cf->file_status != FILE_ADDED) {
391 			(void)rcsnum_tostr(r1, rbuf, sizeof(rbuf));
392 			cvs_printf(" -r%s", rbuf);
393 
394 			if (diff_rev2 != NULL) {
395 				(void)rcsnum_tostr(diff_rev2, rbuf,
396 				    sizeof(rbuf));
397 				cvs_printf(" -r%s", rbuf);
398 			}
399 		}
400 
401 		if (diff_rev2 == NULL)
402 			cvs_printf(" %s", cf->file_name);
403 		cvs_printf("\n");
404 	} else {
405 		cvs_printf("diff ");
406 		switch (diff_format) {
407 		case D_CONTEXT:
408 			cvs_printf("-c ");
409 			break;
410 		case D_RCSDIFF:
411 			cvs_printf("-n ");
412 			break;
413 		case D_UNIFIED:
414 			cvs_printf("-u ");
415 			break;
416 		default:
417 			break;
418 		}
419 		if (diff_rev1 == NULL) {
420 			cvs_printf("%s ", CVS_PATH_DEVNULL);
421 		} else {
422 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
423 			cvs_printf("%s:%s ", cf->file_path, rbuf);
424 		}
425 
426 		if (diff_rev2 == NULL) {
427 			cvs_printf("%s:removed\n", cf->file_path);
428 		} else {
429 			(void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 :
430 			    cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
431 			cvs_printf("%s:%s\n", cf->file_path, rbuf);
432 		}
433 	}
434 
435 	if (cf->file_status == FILE_ADDED ||
436 	    (cvs_cmdop == CVS_OP_RDIFF && diff_rev1 == NULL)) {
437 		xfree(p1);
438 		close(fd1);
439 		(void)xasprintf(&p1, "%s", CVS_PATH_DEVNULL);
440 		if ((fd1 = open(p1, O_RDONLY)) == -1)
441 			fatal("cvs_diff_local: cannot open %s",
442 			    CVS_PATH_DEVNULL);
443 	} else if (cf->file_status == FILE_REMOVED ||
444 	    (cvs_cmdop == CVS_OP_RDIFF && diff_rev2 == NULL)) {
445 		xfree(p2);
446 		close(fd2);
447 		(void)xasprintf(&p2, "%s", CVS_PATH_DEVNULL);
448 		if ((fd2 = open(p2, O_RDONLY)) == -1)
449 			fatal("cvs_diff_local: cannot open %s",
450 			    CVS_PATH_DEVNULL);
451 	}
452 
453 	if (cvs_diffreg(p1, p2, fd1, fd2, NULL) == D_ERROR)
454 		fatal("cvs_diff_local: failed to get RCS patch");
455 
456 	close(fd1);
457 	close(fd2);
458 
459 	cvs_worklist_run(&temp_files, cvs_worklist_unlink);
460 
461 	if (p1 != NULL)
462 		xfree(p1);
463 	if (p2 != NULL)
464 		xfree(p2);
465 
466 	if (diff_rev1 != NULL && diff_rev1 != cf->file_rcs->rf_head &&
467 	    (cf->file_ent != NULL && diff_rev1 != cf->file_ent->ce_rev))
468 		rcsnum_free(diff_rev1);
469 	if (diff_rev2 != NULL)
470 		rcsnum_free(diff_rev2);
471 
472 	diff_rev1 = diff_rev2 = NULL;
473 }
474