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