xref: /openbsd-src/usr.bin/cvs/diff.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /*	$OpenBSD: diff.c,v 1.151 2009/04/03 19:46:56 joris Exp $	*/
2 /*
3  * Copyright (c) 2008 Tobias Stoeckmann <tobias@openbsd.org>
4  * Copyright (c) 2006 Joris Vink <joris@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 #include <sys/time.h>
21 
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <unistd.h>
28 
29 #include "cvs.h"
30 #include "diff.h"
31 #include "remote.h"
32 
33 void	cvs_diff_local(struct cvs_file *);
34 
35 static int	 Nflag = 0;
36 static int	 force_head = 0;
37 static char	*koptstr;
38 static char	*rev1 = NULL;
39 static char	*rev2 = NULL;
40 static time_t	 date1 = -1;
41 static time_t	 date2 = -1;
42 static char	*dateflag1 = NULL;
43 static char	*dateflag2 = NULL;
44 
45 struct cvs_cmd cvs_cmd_diff = {
46 	CVS_OP_DIFF, CVS_USE_WDIR, "diff",
47 	{ "di", "dif" },
48 	"Show differences between revisions",
49 	"[-cilNnpRu] [[-D date] [-r rev] [-D date2 | -r rev2]] "
50 	"[-k mode] [file ...]",
51 	"cfD:ik:lNnpr:Ru",
52 	NULL,
53 	cvs_diff
54 };
55 
56 struct cvs_cmd cvs_cmd_rdiff = {
57 	CVS_OP_RDIFF, 0, "rdiff",
58 	{ "patch", "pa" },
59 	"Show differences between revisions",
60 	"[-flR] [-c | -u] [-s | -t] [-V ver] -D date | -r rev\n"
61 	"[-D date2 | -r rev2] [-k mode] module ...",
62 	"cfD:k:lr:RuV:",
63 	NULL,
64 	cvs_diff
65 };
66 
67 int
68 cvs_diff(int argc, char **argv)
69 {
70 	int ch, flags;
71 	char *arg = ".";
72 	struct cvs_recursion cr;
73 
74 	flags = CR_RECURSE_DIRS;
75 	strlcpy(diffargs, cvs_cmdop == CVS_OP_DIFF ? "diff" : "rdiff",
76 	    sizeof(diffargs));
77 
78 	while ((ch = getopt(argc, argv, cvs_cmdop == CVS_OP_DIFF ?
79 	    cvs_cmd_diff.cmd_opts : cvs_cmd_rdiff.cmd_opts)) != -1) {
80 		switch (ch) {
81 		case 'c':
82 			strlcat(diffargs, " -c", sizeof(diffargs));
83 			diff_format = D_CONTEXT;
84 			break;
85 		case 'D':
86 			if (date1 == -1 && rev1 == NULL) {
87 				date1 = cvs_date_parse(optarg);
88 				dateflag1 = optarg;
89 			} else if (date2 == -1 && rev2 == NULL) {
90 				date2 = cvs_date_parse(optarg);
91 				dateflag2 = optarg;
92 			} else {
93 				fatal("no more than 2 revisions/dates can"
94 				    " be specified");
95 			}
96 			break;
97 		case 'f':
98 			force_head = 1;
99 			break;
100 		case 'i':
101 			strlcat(diffargs, " -i", sizeof(diffargs));
102 			diff_iflag = 1;
103 			break;
104 		case 'k':
105 			koptstr = optarg;
106 			kflag = rcs_kflag_get(koptstr);
107 			if (RCS_KWEXP_INVAL(kflag)) {
108 				cvs_log(LP_ERR,
109 				    "invalid RCS keyword expansion mode");
110 				fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
111 				    cvs_cmd_diff.cmd_synopsis :
112 				    cvs_cmd_rdiff.cmd_synopsis);
113 			}
114 			break;
115 		case 'l':
116 			flags &= ~CR_RECURSE_DIRS;
117 			break;
118 		case 'n':
119 			strlcat(diffargs, " -n", sizeof(diffargs));
120 			diff_format = D_RCSDIFF;
121 			break;
122 		case 'N':
123 			strlcat(diffargs, " -N", sizeof(diffargs));
124 			Nflag = 1;
125 			break;
126 		case 'p':
127 			strlcat(diffargs, " -p", sizeof(diffargs));
128 			diff_pflag = 1;
129 			break;
130 		case 'R':
131 			flags |= CR_RECURSE_DIRS;
132 			break;
133 		case 'r':
134 			if (date1 == -1 && rev1 == NULL) {
135 				rev1 = optarg;
136 			} else if (date2 == -1 && rev2 == NULL) {
137 				rev2 = optarg;
138 			} else {
139 				fatal("no more than 2 revisions/dates can"
140 				    " be specified");
141 			}
142 			break;
143 		case 'u':
144 			strlcat(diffargs, " -u", sizeof(diffargs));
145 			diff_format = D_UNIFIED;
146 			break;
147 		case 'V':
148 			fatal("the -V option is obsolete "
149 			    "and should not be used");
150 		default:
151 			fatal("%s", cvs_cmdop == CVS_OP_DIFF ?
152 			    cvs_cmd_diff.cmd_synopsis :
153 			    cvs_cmd_rdiff.cmd_synopsis);
154 		}
155 	}
156 
157 	argc -= optind;
158 	argv += optind;
159 
160 	cr.enterdir = NULL;
161 	cr.leavedir = NULL;
162 
163 	if (cvs_cmdop == CVS_OP_RDIFF) {
164 		if (rev1 == NULL && rev2 == NULL && dateflag1 == NULL &&
165 		    dateflag2 == NULL)
166 			fatal("must specify at least one revision/date!");
167 
168 		if (!argc)
169 			fatal("%s", cvs_cmd_rdiff.cmd_synopsis);
170 
171 		if (!diff_format) {
172 			strlcat(diffargs, " -c", sizeof(diffargs));
173 			diff_format = D_CONTEXT;
174 		}
175 
176 		flags |= CR_REPO;
177 	}
178 
179 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
180 		cvs_client_connect_to_server();
181 		cr.fileproc = cvs_client_sendfile;
182 
183 		if (!(flags & CR_RECURSE_DIRS))
184 			cvs_client_send_request("Argument -l");
185 
186 		if (kflag)
187 			cvs_client_send_request("Argument -k%s", koptstr);
188 
189 		switch (diff_format) {
190 		case D_CONTEXT:
191 			cvs_client_send_request("Argument -c");
192 			break;
193 		case D_RCSDIFF:
194 			cvs_client_send_request("Argument -n");
195 			break;
196 		case D_UNIFIED:
197 			cvs_client_send_request("Argument -u");
198 			break;
199 		default:
200 			break;
201 		}
202 
203 		if (Nflag == 1)
204 			cvs_client_send_request("Argument -N");
205 
206 		if (diff_pflag == 1)
207 			cvs_client_send_request("Argument -p");
208 
209 		if (rev1 != NULL)
210 			cvs_client_send_request("Argument -r%s", rev1);
211 		if (rev2 != NULL)
212 			cvs_client_send_request("Argument -r%s", rev2);
213 
214 		if (dateflag1 != NULL)
215 			cvs_client_send_request("Argument -D%s", dateflag1);
216 		if (dateflag2 != NULL)
217 			cvs_client_send_request("Argument -D%s", dateflag2);
218 	} else {
219 		if (cvs_cmdop == CVS_OP_RDIFF &&
220 		    chdir(current_cvsroot->cr_dir) == -1)
221 			fatal("cvs_diff: %s", strerror(errno));
222 
223 		cr.fileproc = cvs_diff_local;
224 	}
225 
226 	cr.flags = flags;
227 
228 	diff_rev1 = diff_rev2 = NULL;
229 
230 	if (cvs_cmdop == CVS_OP_DIFF ||
231 	    current_cvsroot->cr_method == CVS_METHOD_LOCAL) {
232 		if (argc > 0)
233 			cvs_file_run(argc, argv, &cr);
234 		else
235 			cvs_file_run(1, &arg, &cr);
236 	}
237 
238 	if (current_cvsroot->cr_method != CVS_METHOD_LOCAL) {
239 		cvs_client_send_files(argv, argc);
240 		cvs_client_senddir(".");
241 
242 		cvs_client_send_request((cvs_cmdop == CVS_OP_RDIFF) ?
243 		    "rdiff" : "diff");
244 
245 		cvs_client_get_responses();
246 	}
247 
248 	return (0);
249 }
250 
251 void
252 cvs_diff_local(struct cvs_file *cf)
253 {
254 	BUF *b1;
255 	int fd1, fd2;
256 	struct stat st;
257 	struct timeval tv[2], tv2[2];
258 	struct tm datetm;
259 	char rbuf[CVS_REV_BUFSZ], tbuf[CVS_TIME_BUFSZ], *p1, *p2;
260 
261 	b1 = NULL;
262 	fd1 = fd2 = -1;
263 	p1 = p2 = NULL;
264 
265 	cvs_log(LP_TRACE, "cvs_diff_local(%s)", cf->file_path);
266 
267 	if (cf->file_type == CVS_DIR) {
268 		if (verbosity > 1)
269 			cvs_log(LP_ERR, "Diffing inside %s", cf->file_path);
270 		return;
271 	}
272 
273 	cvs_file_classify(cf, cvs_directory_tag);
274 
275 	if (cvs_cmdop == CVS_OP_DIFF) {
276 		if (cf->file_ent == NULL) {
277 			cvs_log(LP_ERR, "I know nothing about %s",
278 			    cf->file_path);
279 			return;
280 		}
281 
282 		switch (cf->file_ent->ce_status) {
283 		case CVS_ENT_ADDED:
284 			if (Nflag == 0) {
285 				cvs_log(LP_ERR, "%s is a new entry, no "
286 				    "comparison available", cf->file_path);
287 				return;
288 			}
289 			if (!(cf->file_flags & FILE_ON_DISK)) {
290 				cvs_log(LP_ERR, "cannot find %s",
291 				    cf->file_path);
292 				return;
293 			}
294 			break;
295 		case CVS_ENT_REMOVED:
296 			if (Nflag == 0) {
297 				cvs_log(LP_ERR, "%s was removed, no "
298 				    "comparison available", cf->file_path);
299 				return;
300 			}
301 			if (cf->file_rcs == NULL) {
302 				cvs_log(LP_ERR, "cannot find RCS file for %s",
303 				    cf->file_path);
304 				return;
305 			}
306 			break;
307 		default:
308 			if (!(cf->file_flags & FILE_ON_DISK)) {
309 				cvs_printf("? %s\n", cf->file_path);
310 				return;
311 			}
312 
313 			if (cf->file_rcs == NULL) {
314 				cvs_log(LP_ERR, "cannot find RCS file for %s",
315 				    cf->file_path);
316 				return;
317 			}
318 			break;
319 		}
320 	}
321 
322 	if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL &&
323 	    date1 == -1 && date2 == -1)
324 		return;
325 
326 	if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) {
327 		cvs_log(LP_ERR, "no head revision in RCS file for %s\n",
328 		    cf->file_path);
329 		return;
330 	}
331 
332 	if (kflag && cf->file_rcs != NULL)
333 		rcs_kwexp_set(cf->file_rcs, kflag);
334 
335 	if (cf->file_rcs == NULL)
336 		diff_rev1 = NULL;
337 	else if (rev1 != NULL || date1 != -1) {
338 		cvs_specified_date = date1;
339 		diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs);
340 		if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) {
341 			if (rev1 != NULL) {
342 				cvs_log(LP_ERR, "tag %s not in file %s", rev1,
343 				    cf->file_path);
344 				goto cleanup;
345 			} else if (Nflag) {
346 				diff_rev1 = NULL;
347 			} else {
348 				gmtime_r(&cvs_specified_date, &datetm);
349 				strftime(tbuf, sizeof(tbuf),
350 				    "%Y.%m.%d.%H.%M.%S", &datetm);
351 				cvs_log(LP_ERR, "no revision for date %s in "
352 				    "file %s", tbuf, cf->file_path);
353 				goto cleanup;
354 			}
355 		} else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
356 		    force_head) {
357 			/* -f is not allowed for unknown symbols */
358 			if ((diff_rev1 = rcsnum_parse(rev1)) == NULL)
359 				fatal("no such tag %s", rev1);
360 			rcsnum_free(diff_rev1);
361 
362 			diff_rev1 = cf->file_rcs->rf_head;
363 		}
364 		cvs_specified_date = -1;
365 	} else if (cvs_cmdop == CVS_OP_DIFF) {
366 		if (cf->file_ent->ce_status == CVS_ENT_ADDED)
367 			diff_rev1 = NULL;
368 		else
369 			diff_rev1 = cf->file_ent->ce_rev;
370 	}
371 
372 	if (cf->file_rcs == NULL)
373 		diff_rev2 = NULL;
374 	else if (rev2 != NULL || date2 != -1) {
375 		cvs_specified_date = date2;
376 		diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs);
377 		if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) {
378 			if (rev2 != NULL) {
379 				cvs_log(LP_ERR, "tag %s not in file %s", rev2,
380 				    cf->file_path);
381 				goto cleanup;
382 			} else if (Nflag) {
383 				diff_rev2 = NULL;
384 			} else {
385 				gmtime_r(&cvs_specified_date, &datetm);
386 				strftime(tbuf, sizeof(tbuf),
387 				    "%Y.%m.%d.%H.%M.%S", &datetm);
388 				cvs_log(LP_ERR, "no revision for date %s in "
389 				    "file %s", tbuf, cf->file_path);
390 				goto cleanup;
391 			}
392 		} else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
393 		    force_head) {
394 			/* -f is not allowed for unknown symbols */
395 			if ((diff_rev2 = rcsnum_parse(rev2)) == NULL)
396 				fatal("no such tag %s", rev2);
397 			rcsnum_free(diff_rev2);
398 
399 			diff_rev2 = cf->file_rcs->rf_head;
400 		}
401 		cvs_specified_date = -1;
402 	} else if (cvs_cmdop == CVS_OP_RDIFF)
403 		diff_rev2 = cf->file_rcs->rf_head;
404 	else if (cf->file_ent->ce_status == CVS_ENT_REMOVED)
405 		diff_rev2 = NULL;
406 
407 	if (diff_rev1 != NULL && diff_rev2 != NULL &&
408 	    rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0)
409 		goto cleanup;
410 
411 	switch (cvs_cmdop) {
412 	case CVS_OP_DIFF:
413 		if (cf->file_status == FILE_UPTODATE) {
414 			if (diff_rev2 == NULL &&
415 			    !rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0))
416 				goto cleanup;
417 		}
418 		break;
419 	case CVS_OP_RDIFF:
420 		if (diff_rev1 == NULL && diff_rev2 == NULL)
421 			goto cleanup;
422 		break;
423 	}
424 
425 	cvs_printf("Index: %s\n", cf->file_path);
426 	if (cvs_cmdop == CVS_OP_DIFF)
427 		cvs_printf("%s\nRCS file: %s\n", RCS_DIFF_DIV,
428 		    cf->file_rcs != NULL ? cf->file_rpath : cf->file_path);
429 
430 	if (diff_rev1 != NULL) {
431 		if (cvs_cmdop == CVS_OP_DIFF && diff_rev1 != NULL) {
432 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
433 			cvs_printf("retrieving revision %s\n", rbuf);
434 		}
435 
436 		tv[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev1);
437 		tv[0].tv_usec = 0;
438 		tv[1] = tv[0];
439 
440 		(void)xasprintf(&p1, "%s/diff1.XXXXXXXXXX", cvs_tmpdir);
441 		fd1 = rcs_rev_write_stmp(cf->file_rcs, diff_rev1, p1, 0);
442 		if (futimes(fd1, tv) == -1)
443 			fatal("cvs_diff_local: utimes failed");
444 	}
445 
446 	if (diff_rev2 != NULL) {
447 		if (cvs_cmdop == CVS_OP_DIFF && rev2 != NULL) {
448 			(void)rcsnum_tostr(diff_rev2, rbuf, sizeof(rbuf));
449 			cvs_printf("retrieving revision %s\n", rbuf);
450 		}
451 
452 		tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs, diff_rev2);
453 		tv2[0].tv_usec = 0;
454 		tv2[1] = tv2[0];
455 
456 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
457 		fd2 = rcs_rev_write_stmp(cf->file_rcs, diff_rev2, p2, 0);
458 		if (futimes(fd2, tv2) == -1)
459 			fatal("cvs_diff_local: utimes failed");
460 	} else if (cvs_cmdop == CVS_OP_DIFF &&
461 	    (cf->file_flags & FILE_ON_DISK) &&
462 	    cf->file_ent->ce_status != CVS_ENT_REMOVED) {
463 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
464 		if (cvs_server_active == 1 && cf->fd == -1) {
465 			tv2[0].tv_sec = rcs_rev_getdate(cf->file_rcs,
466 			    cf->file_ent->ce_rev);
467 			tv2[0].tv_usec = 0;
468 			tv2[1] = tv2[0];
469 
470 			fd2 = rcs_rev_write_stmp(cf->file_rcs,
471 			    cf->file_ent->ce_rev, p2, 0);
472 			if (futimes(fd2, tv2) == -1)
473 				fatal("cvs_diff_local: futimes failed");
474 		} else {
475 			if (fstat(cf->fd, &st) == -1)
476 				fatal("fstat failed %s", strerror(errno));
477 			b1 = cvs_buf_load_fd(cf->fd);
478 
479 			tv2[0].tv_sec = st.st_mtime;
480 			tv2[0].tv_usec = 0;
481 			tv2[1] = tv2[0];
482 
483 			fd2 = cvs_buf_write_stmp(b1, p2, tv2);
484 			cvs_buf_free(b1);
485 		}
486 	}
487 
488 	switch (cvs_cmdop) {
489 	case CVS_OP_DIFF:
490 		cvs_printf("%s", diffargs);
491 
492 		if (rev1 != NULL && diff_rev1 != NULL) {
493 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
494 			cvs_printf(" -r%s", rbuf);
495 
496 			if (rev2 != NULL && diff_rev2 != NULL) {
497 				(void)rcsnum_tostr(diff_rev2, rbuf,
498 				    sizeof(rbuf));
499 				cvs_printf(" -r%s", rbuf);
500 			}
501 		}
502 
503 		if (diff_rev2 == NULL)
504 			cvs_printf(" %s", cf->file_path);
505 		cvs_printf("\n");
506 		break;
507 	case CVS_OP_RDIFF:
508 		cvs_printf("diff ");
509 		switch (diff_format) {
510 		case D_CONTEXT:
511 			cvs_printf("-c ");
512 			break;
513 		case D_RCSDIFF:
514 			cvs_printf("-n ");
515 			break;
516 		case D_UNIFIED:
517 			cvs_printf("-u ");
518 			break;
519 		default:
520 			break;
521 		}
522 		if (diff_rev1 == NULL) {
523 			cvs_printf("%s ", CVS_PATH_DEVNULL);
524 		} else {
525 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
526 			cvs_printf("%s:%s ", cf->file_path, rbuf);
527 		}
528 
529 		if (diff_rev2 == NULL) {
530 			cvs_printf("%s:removed\n", cf->file_path);
531 		} else {
532 			(void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 :
533 			    cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
534 			cvs_printf("%s:%s\n", cf->file_path, rbuf);
535 		}
536 		break;
537 	}
538 
539 	if (fd1 == -1) {
540 		if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
541 			fatal("cannot open %s", CVS_PATH_DEVNULL);
542 	}
543 	if (fd2 == -1) {
544 		if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
545 			fatal("cannot open %s", CVS_PATH_DEVNULL);
546 	}
547 
548 	if (cvs_diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL,
549 	    p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL)
550 	    == D_ERROR)
551 		fatal("cvs_diff_local: failed to get RCS patch");
552 
553 	close(fd1);
554 	close(fd2);
555 
556 	cvs_worklist_run(&temp_files, cvs_worklist_unlink);
557 
558 	if (p1 != NULL)
559 		xfree(p1);
560 	if (p2 != NULL)
561 		xfree(p2);
562 
563 cleanup:
564 	if (diff_rev1 != NULL &&
565 	    (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) &&
566 	    (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev))
567 		xfree(diff_rev1);
568 	diff_rev1 = NULL;
569 
570 	if (diff_rev2 != NULL &&
571 	    (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head))
572 		xfree(diff_rev2);
573 	diff_rev2 = NULL;
574 }
575