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