xref: /openbsd-src/usr.bin/cvs/diff.c (revision d874cce4b1d9fe6b41c9e4f2117a77d8a4a37b92)
1 /*	$OpenBSD: diff.c,v 1.144 2008/06/20 14:04:29 tobias 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_NOTICE, "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->fd == -1) {
290 				if (!cvs_server_active)
291 					cvs_log(LP_ERR, "cannot find %s",
292 					    cf->file_path);
293 				return;
294 			}
295 			break;
296 		case CVS_ENT_REMOVED:
297 			if (Nflag == 0) {
298 				cvs_log(LP_ERR, "%s was removed, no "
299 				    "comparison available", cf->file_path);
300 				return;
301 			}
302 			if (cf->file_rcs == NULL) {
303 				cvs_log(LP_ERR, "cannot find RCS file for %s",
304 				    cf->file_path);
305 				return;
306 			}
307 			break;
308 		default:
309 			if (cvs_server_active != 1 && cf->fd == -1) {
310 				cvs_log(LP_ERR, "cannot find %s",
311 					    cf->file_path);
312 				return;
313 			}
314 
315 			if (cf->file_rcs == NULL) {
316 				cvs_log(LP_ERR, "cannot find RCS file for %s",
317 				    cf->file_path);
318 				return;
319 			}
320 			break;
321 		}
322 	}
323 
324 	if (cf->file_status == FILE_UPTODATE && rev1 == NULL && rev2 == NULL &&
325 	    date1 == -1 && date2 == -1)
326 		return;
327 
328 	if (cf->file_rcs != NULL && cf->file_rcs->rf_head == NULL) {
329 		cvs_log(LP_ERR, "no head revision in RCS file for %s\n",
330 		    cf->file_path);
331 		return;
332 	}
333 
334 	if (kflag && cf->file_rcs != NULL)
335 		rcs_kwexp_set(cf->file_rcs, kflag);
336 
337 	if (cf->file_rcs == NULL)
338 		diff_rev1 = NULL;
339 	else if (rev1 != NULL || date1 != -1) {
340 		cvs_specified_date = date1;
341 		diff_rev1 = rcs_translate_tag(rev1, cf->file_rcs);
342 		if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_DIFF) {
343 			if (rev1 != NULL) {
344 				cvs_log(LP_ERR, "tag %s not in file %s", rev1,
345 				    cf->file_path);
346 				goto cleanup;
347 			} else if (Nflag) {
348 				diff_rev1 = NULL;
349 			} else {
350 				gmtime_r(&cvs_specified_date, &datetm);
351 				strftime(tbuf, sizeof(tbuf),
352 				    "%Y.%m.%d.%H.%M.%S", &datetm);
353 				cvs_log(LP_ERR, "no revision for date %s in "
354 				    "file %s", tbuf, cf->file_path);
355 				goto cleanup;
356 			}
357 		} else if (diff_rev1 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
358 		    force_head) {
359 			/* -f is not allowed for unknown symbols */
360 			if ((diff_rev1 = rcsnum_parse(rev1)) == NULL)
361 				fatal("no such tag %s", rev1);
362 			rcsnum_free(diff_rev1);
363 
364 			diff_rev1 = cf->file_rcs->rf_head;
365 		}
366 		cvs_specified_date = -1;
367 	} else if (cvs_cmdop == CVS_OP_DIFF) {
368 		if (cf->file_ent->ce_status == CVS_ENT_ADDED)
369 			diff_rev1 = NULL;
370 		else
371 			diff_rev1 = cf->file_ent->ce_rev;
372 	}
373 
374 	if (cf->file_rcs == NULL)
375 		diff_rev2 = NULL;
376 	else if (rev2 != NULL || date2 != -1) {
377 		cvs_specified_date = date2;
378 		diff_rev2 = rcs_translate_tag(rev2, cf->file_rcs);
379 		if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_DIFF) {
380 			if (rev2 != NULL) {
381 				cvs_log(LP_ERR, "tag %s not in file %s", rev2,
382 				    cf->file_path);
383 				goto cleanup;
384 			} else if (Nflag) {
385 				diff_rev2 = NULL;
386 			} else {
387 				gmtime_r(&cvs_specified_date, &datetm);
388 				strftime(tbuf, sizeof(tbuf),
389 				    "%Y.%m.%d.%H.%M.%S", &datetm);
390 				cvs_log(LP_ERR, "no revision for date %s in "
391 				    "file %s", tbuf, cf->file_path);
392 				goto cleanup;
393 			}
394 		} else if (diff_rev2 == NULL && cvs_cmdop == CVS_OP_RDIFF &&
395 		    force_head) {
396 			/* -f is not allowed for unknown symbols */
397 			if ((diff_rev2 = rcsnum_parse(rev2)) == NULL)
398 				fatal("no such tag %s", rev2);
399 			rcsnum_free(diff_rev2);
400 
401 			diff_rev2 = cf->file_rcs->rf_head;
402 		}
403 		cvs_specified_date = -1;
404 	} else if (cvs_cmdop == CVS_OP_RDIFF)
405 		diff_rev2 = cf->file_rcs->rf_head;
406 	else if (cf->file_ent->ce_status == CVS_ENT_REMOVED)
407 		diff_rev2 = NULL;
408 
409 	if (diff_rev1 != NULL && diff_rev2 != NULL &&
410 	    rcsnum_cmp(diff_rev1, diff_rev2, 0) == 0)
411 		goto cleanup;
412 
413 	switch (cvs_cmdop) {
414 	case CVS_OP_DIFF:
415 		if (cf->file_status == FILE_UPTODATE && diff_rev1 != NULL &&
416 		    rcsnum_cmp(diff_rev1, cf->file_rcsrev, 0) == 0)
417 			goto cleanup;
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 && cf->fd != -1 &&
461 	    cf->file_ent->ce_status != CVS_ENT_REMOVED) {
462 		if (fstat(cf->fd, &st) == -1)
463 			fatal("fstat failed %s", strerror(errno));
464 		b1 = cvs_buf_load_fd(cf->fd);
465 
466 		tv2[0].tv_sec = st.st_mtime;
467 		tv2[0].tv_usec = 0;
468 		tv2[1] = tv2[0];
469 
470 		(void)xasprintf(&p2, "%s/diff2.XXXXXXXXXX", cvs_tmpdir);
471 		fd2 = cvs_buf_write_stmp(b1, p2, tv2);
472 		cvs_buf_free(b1);
473 	}
474 
475 	switch (cvs_cmdop) {
476 	case CVS_OP_DIFF:
477 		cvs_printf("%s", diffargs);
478 
479 		if (rev1 != NULL && diff_rev1 != NULL) {
480 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
481 			cvs_printf(" -r%s", rbuf);
482 
483 			if (rev2 != NULL && diff_rev2 != NULL) {
484 				(void)rcsnum_tostr(diff_rev2, rbuf,
485 				    sizeof(rbuf));
486 				cvs_printf(" -r%s", rbuf);
487 			}
488 		}
489 
490 		if (diff_rev2 == NULL)
491 			cvs_printf(" %s", cf->file_path);
492 		cvs_printf("\n");
493 		break;
494 	case CVS_OP_RDIFF:
495 		cvs_printf("diff ");
496 		switch (diff_format) {
497 		case D_CONTEXT:
498 			cvs_printf("-c ");
499 			break;
500 		case D_RCSDIFF:
501 			cvs_printf("-n ");
502 			break;
503 		case D_UNIFIED:
504 			cvs_printf("-u ");
505 			break;
506 		default:
507 			break;
508 		}
509 		if (diff_rev1 == NULL) {
510 			cvs_printf("%s ", CVS_PATH_DEVNULL);
511 		} else {
512 			(void)rcsnum_tostr(diff_rev1, rbuf, sizeof(rbuf));
513 			cvs_printf("%s:%s ", cf->file_path, rbuf);
514 		}
515 
516 		if (diff_rev2 == NULL) {
517 			cvs_printf("%s:removed\n", cf->file_path);
518 		} else {
519 			(void)rcsnum_tostr(diff_rev2 != NULL ? diff_rev2 :
520 			    cf->file_rcs->rf_head, rbuf, sizeof(rbuf));
521 			cvs_printf("%s:%s\n", cf->file_path, rbuf);
522 		}
523 		break;
524 	}
525 
526 	if (fd1 == -1) {
527 		if ((fd1 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
528 			fatal("cannot open %s", CVS_PATH_DEVNULL);
529 	}
530 	if (fd2 == -1) {
531 		if ((fd2 = open(CVS_PATH_DEVNULL, O_RDONLY, 0)) == -1)
532 			fatal("cannot open %s", CVS_PATH_DEVNULL);
533 	}
534 
535 	if (cvs_diffreg(p1 != NULL ? cf->file_path : CVS_PATH_DEVNULL,
536 	    p2 != NULL ? cf->file_path : CVS_PATH_DEVNULL, fd1, fd2, NULL)
537 	    == D_ERROR)
538 		fatal("cvs_diff_local: failed to get RCS patch");
539 
540 	close(fd1);
541 	close(fd2);
542 
543 	cvs_worklist_run(&temp_files, cvs_worklist_unlink);
544 
545 	if (p1 != NULL)
546 		xfree(p1);
547 	if (p2 != NULL)
548 		xfree(p2);
549 
550 cleanup:
551 	if (diff_rev1 != NULL &&
552 	    (cf->file_rcs == NULL || diff_rev1 != cf->file_rcs->rf_head) &&
553 	    (cf->file_ent == NULL || diff_rev1 != cf->file_ent->ce_rev))
554 		xfree(diff_rev1);
555 	diff_rev1 = NULL;
556 
557 	if (diff_rev2 != NULL &&
558 	    (cf->file_rcs == NULL || diff_rev2 != cf->file_rcs->rf_head))
559 		xfree(diff_rev2);
560 	diff_rev2 = NULL;
561 }
562