1 |
|
|
/* $OpenBSD: rcsdiff.c,v 1.84 2015/11/02 16:45:21 nicm Exp $ */ |
2 |
|
|
/* |
3 |
|
|
* Copyright (c) 2005 Joris Vink <joris@openbsd.org> |
4 |
|
|
* All rights reserved. |
5 |
|
|
* |
6 |
|
|
* Redistribution and use in source and binary forms, with or without |
7 |
|
|
* modification, are permitted provided that the following conditions |
8 |
|
|
* are met: |
9 |
|
|
* |
10 |
|
|
* 1. Redistributions of source code must retain the above copyright |
11 |
|
|
* notice, this list of conditions and the following disclaimer. |
12 |
|
|
* 2. The name of the author may not be used to endorse or promote products |
13 |
|
|
* derived from this software without specific prior written permission. |
14 |
|
|
* |
15 |
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, |
16 |
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
17 |
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
18 |
|
|
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 |
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 |
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
21 |
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
22 |
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
23 |
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
24 |
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 |
|
|
*/ |
26 |
|
|
|
27 |
|
|
#include <sys/stat.h> |
28 |
|
|
#include <sys/time.h> |
29 |
|
|
|
30 |
|
|
#include <err.h> |
31 |
|
|
#include <fcntl.h> |
32 |
|
|
#include <stdio.h> |
33 |
|
|
#include <stdlib.h> |
34 |
|
|
#include <string.h> |
35 |
|
|
#include <unistd.h> |
36 |
|
|
|
37 |
|
|
#include "rcsprog.h" |
38 |
|
|
#include "diff.h" |
39 |
|
|
|
40 |
|
|
static int rcsdiff_file(RCSFILE *, RCSNUM *, const char *, int); |
41 |
|
|
static int rcsdiff_rev(RCSFILE *, RCSNUM *, RCSNUM *, int); |
42 |
|
|
static void push_ignore_pats(char *); |
43 |
|
|
|
44 |
|
|
static int quiet; |
45 |
|
|
static int kflag = RCS_KWEXP_ERR; |
46 |
|
|
static char *diff_ignore_pats; |
47 |
|
|
|
48 |
|
|
int |
49 |
|
|
rcsdiff_main(int argc, char **argv) |
50 |
|
|
{ |
51 |
|
|
int fd, i, ch, dflags, status; |
52 |
|
|
RCSNUM *rev1, *rev2; |
53 |
|
|
RCSFILE *file; |
54 |
|
|
char fpath[PATH_MAX], *rev_str1, *rev_str2; |
55 |
|
|
const char *errstr; |
56 |
|
|
|
57 |
|
|
rev1 = rev2 = NULL; |
58 |
|
|
rev_str1 = rev_str2 = NULL; |
59 |
|
|
status = D_SAME; |
60 |
|
|
dflags = 0; |
61 |
|
|
|
62 |
|
|
if (strlcpy(diffargs, "diff", sizeof(diffargs)) >= sizeof(diffargs)) |
63 |
|
|
errx(D_ERROR, "diffargs too long"); |
64 |
|
|
|
65 |
|
|
while ((ch = rcs_getopt(argc, argv, "abC:cdI:ik:npqr:TtU:uVwx::z::")) != -1) { |
66 |
|
|
switch (ch) { |
67 |
|
|
case 'a': |
68 |
|
|
if (strlcat(diffargs, " -a", sizeof(diffargs)) >= |
69 |
|
|
sizeof(diffargs)) |
70 |
|
|
errx(D_ERROR, "diffargs too long"); |
71 |
|
|
dflags |= D_FORCEASCII; |
72 |
|
|
break; |
73 |
|
|
case 'b': |
74 |
|
|
if (strlcat(diffargs, " -b", sizeof(diffargs)) >= |
75 |
|
|
sizeof(diffargs)) |
76 |
|
|
errx(D_ERROR, "diffargs too long"); |
77 |
|
|
dflags |= D_FOLDBLANKS; |
78 |
|
|
break; |
79 |
|
|
case 'C': |
80 |
|
|
(void)strlcat(diffargs, " -C", sizeof(diffargs)); |
81 |
|
|
if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= |
82 |
|
|
sizeof(diffargs)) |
83 |
|
|
errx(D_ERROR, "diffargs too long"); |
84 |
|
|
diff_context = strtonum(rcs_optarg, 0, INT_MAX, &errstr); |
85 |
|
|
if (errstr) |
86 |
|
|
errx(D_ERROR, "context is %s: %s", |
87 |
|
|
errstr, rcs_optarg); |
88 |
|
|
diff_format = D_CONTEXT; |
89 |
|
|
break; |
90 |
|
|
case 'c': |
91 |
|
|
if (strlcat(diffargs, " -c", sizeof(diffargs)) >= |
92 |
|
|
sizeof(diffargs)) |
93 |
|
|
errx(D_ERROR, "diffargs too long"); |
94 |
|
|
diff_format = D_CONTEXT; |
95 |
|
|
break; |
96 |
|
|
case 'd': |
97 |
|
|
if (strlcat(diffargs, " -d", sizeof(diffargs)) >= |
98 |
|
|
sizeof(diffargs)) |
99 |
|
|
errx(D_ERROR, "diffargs too long"); |
100 |
|
|
dflags |= D_MINIMAL; |
101 |
|
|
break; |
102 |
|
|
case 'i': |
103 |
|
|
if (strlcat(diffargs, " -i", sizeof(diffargs)) >= |
104 |
|
|
sizeof(diffargs)) |
105 |
|
|
errx(D_ERROR, "diffargs too long"); |
106 |
|
|
dflags |= D_IGNORECASE; |
107 |
|
|
break; |
108 |
|
|
case 'I': |
109 |
|
|
(void)strlcat(diffargs, " -I", sizeof(diffargs)); |
110 |
|
|
if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= |
111 |
|
|
sizeof(diffargs)) |
112 |
|
|
errx(D_ERROR, "diffargs too long"); |
113 |
|
|
push_ignore_pats(rcs_optarg); |
114 |
|
|
break; |
115 |
|
|
case 'k': |
116 |
|
|
kflag = rcs_kflag_get(rcs_optarg); |
117 |
|
|
if (RCS_KWEXP_INVAL(kflag)) { |
118 |
|
|
warnx("invalid RCS keyword substitution mode"); |
119 |
|
|
(usage)(); |
120 |
|
|
} |
121 |
|
|
break; |
122 |
|
|
case 'n': |
123 |
|
|
if (strlcat(diffargs, " -n", sizeof(diffargs)) >= |
124 |
|
|
sizeof(diffargs)) |
125 |
|
|
errx(D_ERROR, "diffargs too long"); |
126 |
|
|
diff_format = D_RCSDIFF; |
127 |
|
|
break; |
128 |
|
|
case 'p': |
129 |
|
|
if (strlcat(diffargs, " -p", sizeof(diffargs)) >= |
130 |
|
|
sizeof(diffargs)) |
131 |
|
|
errx(D_ERROR, "diffargs too long"); |
132 |
|
|
dflags |= D_PROTOTYPE; |
133 |
|
|
break; |
134 |
|
|
case 'q': |
135 |
|
|
quiet = 1; |
136 |
|
|
break; |
137 |
|
|
case 'r': |
138 |
|
|
rcs_setrevstr2(&rev_str1, &rev_str2, rcs_optarg); |
139 |
|
|
break; |
140 |
|
|
case 'T': |
141 |
|
|
/* |
142 |
|
|
* kept for compatibility |
143 |
|
|
*/ |
144 |
|
|
break; |
145 |
|
|
case 't': |
146 |
|
|
if (strlcat(diffargs, " -t", sizeof(diffargs)) >= |
147 |
|
|
sizeof(diffargs)) |
148 |
|
|
errx(D_ERROR, "diffargs too long"); |
149 |
|
|
dflags |= D_EXPANDTABS; |
150 |
|
|
break; |
151 |
|
|
case 'U': |
152 |
|
|
(void)strlcat(diffargs, " -U", sizeof(diffargs)); |
153 |
|
|
if (strlcat(diffargs, rcs_optarg, sizeof(diffargs)) >= |
154 |
|
|
sizeof(diffargs)) |
155 |
|
|
errx(D_ERROR, "diffargs too long"); |
156 |
|
|
diff_context = strtonum(rcs_optarg, 0, INT_MAX, &errstr); |
157 |
|
|
if (errstr) |
158 |
|
|
errx(D_ERROR, "context is %s: %s", |
159 |
|
|
errstr, rcs_optarg); |
160 |
|
|
diff_format = D_UNIFIED; |
161 |
|
|
break; |
162 |
|
|
case 'u': |
163 |
|
|
if (strlcat(diffargs, " -u", sizeof(diffargs)) >= |
164 |
|
|
sizeof(diffargs)) |
165 |
|
|
errx(D_ERROR, "diffargs too long"); |
166 |
|
|
diff_format = D_UNIFIED; |
167 |
|
|
break; |
168 |
|
|
case 'V': |
169 |
|
|
printf("%s\n", rcs_version); |
170 |
|
|
exit(0); |
171 |
|
|
case 'w': |
172 |
|
|
if (strlcat(diffargs, " -w", sizeof(diffargs)) >= |
173 |
|
|
sizeof(diffargs)) |
174 |
|
|
errx(D_ERROR, "diffargs too long"); |
175 |
|
|
dflags |= D_IGNOREBLANKS; |
176 |
|
|
break; |
177 |
|
|
case 'x': |
178 |
|
|
/* Use blank extension if none given. */ |
179 |
|
|
rcs_suffixes = rcs_optarg ? rcs_optarg : ""; |
180 |
|
|
break; |
181 |
|
|
case 'z': |
182 |
|
|
timezone_flag = rcs_optarg; |
183 |
|
|
break; |
184 |
|
|
default: |
185 |
|
|
(usage)(); |
186 |
|
|
} |
187 |
|
|
} |
188 |
|
|
|
189 |
|
|
argc -= rcs_optind; |
190 |
|
|
argv += rcs_optind; |
191 |
|
|
|
192 |
|
|
if (argc == 0) { |
193 |
|
|
warnx("no input file"); |
194 |
|
|
(usage)(); |
195 |
|
|
} |
196 |
|
|
|
197 |
|
|
if (diff_ignore_pats != NULL) { |
198 |
|
|
char buf[BUFSIZ]; |
199 |
|
|
int error; |
200 |
|
|
|
201 |
|
|
diff_ignore_re = xmalloc(sizeof(*diff_ignore_re)); |
202 |
|
|
if ((error = regcomp(diff_ignore_re, diff_ignore_pats, |
203 |
|
|
REG_NEWLINE | REG_EXTENDED)) != 0) { |
204 |
|
|
regerror(error, diff_ignore_re, buf, sizeof(buf)); |
205 |
|
|
if (*diff_ignore_pats != '\0') |
206 |
|
|
errx(D_ERROR, "%s: %s", diff_ignore_pats, buf); |
207 |
|
|
else |
208 |
|
|
errx(D_ERROR, "%s", buf); |
209 |
|
|
} |
210 |
|
|
} |
211 |
|
|
|
212 |
|
|
for (i = 0; i < argc; i++) { |
213 |
|
|
fd = rcs_choosefile(argv[i], fpath, sizeof(fpath)); |
214 |
|
|
if (fd < 0) { |
215 |
|
|
warn("%s", fpath); |
216 |
|
|
continue; |
217 |
|
|
} |
218 |
|
|
|
219 |
|
|
if ((file = rcs_open(fpath, fd, |
220 |
|
|
RCS_READ|RCS_PARSE_FULLY)) == NULL) |
221 |
|
|
continue; |
222 |
|
|
|
223 |
|
|
rcs_kwexp_set(file, kflag); |
224 |
|
|
|
225 |
|
|
if (rev_str1 != NULL) { |
226 |
|
|
if ((rev1 = rcs_getrevnum(rev_str1, file)) == NULL) |
227 |
|
|
errx(D_ERROR, "bad revision number"); |
228 |
|
|
} |
229 |
|
|
if (rev_str2 != NULL) { |
230 |
|
|
if ((rev2 = rcs_getrevnum(rev_str2, file)) == NULL) |
231 |
|
|
errx(D_ERROR, "bad revision number"); |
232 |
|
|
} |
233 |
|
|
|
234 |
|
|
if (!quiet) { |
235 |
|
|
fprintf(stderr, "%s\n", RCS_DIFF_DIV); |
236 |
|
|
fprintf(stderr, "RCS file: %s\n", fpath); |
237 |
|
|
} |
238 |
|
|
|
239 |
|
|
diff_file = argv[i]; |
240 |
|
|
|
241 |
|
|
/* No revisions given. */ |
242 |
|
|
if (rev_str1 == NULL) |
243 |
|
|
status = rcsdiff_file(file, file->rf_head, argv[i], |
244 |
|
|
dflags); |
245 |
|
|
/* One revision given. */ |
246 |
|
|
else if (rev_str2 == NULL) |
247 |
|
|
status = rcsdiff_file(file, rev1, argv[i], dflags); |
248 |
|
|
/* Two revisions given. */ |
249 |
|
|
else |
250 |
|
|
status = rcsdiff_rev(file, rev1, rev2, dflags); |
251 |
|
|
|
252 |
|
|
rcs_close(file); |
253 |
|
|
rcsnum_free(rev1); |
254 |
|
|
rcsnum_free(rev2); |
255 |
|
|
rev1 = rev2 = NULL; |
256 |
|
|
} |
257 |
|
|
|
258 |
|
|
return (status); |
259 |
|
|
} |
260 |
|
|
|
261 |
|
|
__dead void |
262 |
|
|
rcsdiff_usage(void) |
263 |
|
|
{ |
264 |
|
|
fprintf(stderr, |
265 |
|
|
"usage: rcsdiff [-cnquV] [-kmode] [-rrev] [-xsuffixes] [-ztz]\n" |
266 |
|
|
" [diff_options] file ...\n"); |
267 |
|
|
|
268 |
|
|
exit(D_ERROR); |
269 |
|
|
} |
270 |
|
|
|
271 |
|
|
static int |
272 |
|
|
rcsdiff_file(RCSFILE *file, RCSNUM *rev, const char *filename, int dflags) |
273 |
|
|
{ |
274 |
|
|
int ret, fd; |
275 |
|
|
time_t t; |
276 |
|
|
struct stat st; |
277 |
|
|
char *path1, *path2; |
278 |
|
|
BUF *b1, *b2; |
279 |
|
|
char rbuf[RCS_REV_BUFSZ]; |
280 |
|
|
struct tm *tb; |
281 |
|
|
struct timeval tv[2], tv2[2]; |
282 |
|
|
|
283 |
|
|
memset(&tv, 0, sizeof(tv)); |
284 |
|
|
memset(&tv2, 0, sizeof(tv2)); |
285 |
|
|
|
286 |
|
|
ret = D_ERROR; |
287 |
|
|
b1 = b2 = NULL; |
288 |
|
|
|
289 |
|
|
diff_rev1 = rev; |
290 |
|
|
diff_rev2 = NULL; |
291 |
|
|
path1 = path2 = NULL; |
292 |
|
|
|
293 |
|
|
if ((fd = open(filename, O_RDONLY)) == -1) { |
294 |
|
|
warn("%s", filename); |
295 |
|
|
goto out; |
296 |
|
|
} |
297 |
|
|
|
298 |
|
|
rcsnum_tostr(rev, rbuf, sizeof(rbuf)); |
299 |
|
|
if (!quiet) { |
300 |
|
|
fprintf(stderr, "retrieving revision %s\n", rbuf); |
301 |
|
|
fprintf(stderr, "%s -r%s %s\n", diffargs, rbuf, filename); |
302 |
|
|
} |
303 |
|
|
|
304 |
|
|
if ((b1 = rcs_getrev(file, rev)) == NULL) { |
305 |
|
|
warnx("failed to retrieve revision %s", rbuf); |
306 |
|
|
goto out; |
307 |
|
|
} |
308 |
|
|
|
309 |
|
|
b1 = rcs_kwexp_buf(b1, file, rev); |
310 |
|
|
tv[0].tv_sec = rcs_rev_getdate(file, rev); |
311 |
|
|
tv[1].tv_sec = tv[0].tv_sec; |
312 |
|
|
|
313 |
|
|
if ((b2 = buf_load(filename)) == NULL) { |
314 |
|
|
warnx("failed to load file: `%s'", filename); |
315 |
|
|
goto out; |
316 |
|
|
} |
317 |
|
|
|
318 |
|
|
/* XXX - GNU uses GMT */ |
319 |
|
|
if (fstat(fd, &st) == -1) |
320 |
|
|
err(D_ERROR, "%s", filename); |
321 |
|
|
|
322 |
|
|
tb = gmtime(&st.st_mtime); |
323 |
|
|
t = mktime(tb); |
324 |
|
|
|
325 |
|
|
tv2[0].tv_sec = t; |
326 |
|
|
tv2[1].tv_sec = t; |
327 |
|
|
|
328 |
|
|
(void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); |
329 |
|
|
buf_write_stmp(b1, path1); |
330 |
|
|
|
331 |
|
|
buf_free(b1); |
332 |
|
|
b1 = NULL; |
333 |
|
|
|
334 |
|
|
if (utimes(path1, (const struct timeval *)&tv) < 0) |
335 |
|
|
warn("utimes"); |
336 |
|
|
|
337 |
|
|
(void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); |
338 |
|
|
buf_write_stmp(b2, path2); |
339 |
|
|
|
340 |
|
|
buf_free(b2); |
341 |
|
|
b2 = NULL; |
342 |
|
|
|
343 |
|
|
if (utimes(path2, (const struct timeval *)&tv2) < 0) |
344 |
|
|
warn("utimes"); |
345 |
|
|
|
346 |
|
|
ret = diffreg(path1, path2, NULL, dflags); |
347 |
|
|
|
348 |
|
|
out: |
349 |
|
|
if (fd != -1) |
350 |
|
|
(void)close(fd); |
351 |
|
|
buf_free(b1); |
352 |
|
|
buf_free(b2); |
353 |
|
|
free(path1); |
354 |
|
|
free(path2); |
355 |
|
|
|
356 |
|
|
return (ret); |
357 |
|
|
} |
358 |
|
|
|
359 |
|
|
static int |
360 |
|
|
rcsdiff_rev(RCSFILE *file, RCSNUM *rev1, RCSNUM *rev2, int dflags) |
361 |
|
|
{ |
362 |
|
|
struct timeval tv[2], tv2[2]; |
363 |
|
|
BUF *b1, *b2; |
364 |
|
|
int ret; |
365 |
|
|
char *path1, *path2, rbuf1[RCS_REV_BUFSZ], rbuf2[RCS_REV_BUFSZ]; |
366 |
|
|
|
367 |
|
|
ret = D_ERROR; |
368 |
|
|
b1 = b2 = NULL; |
369 |
|
|
memset(&tv, 0, sizeof(tv)); |
370 |
|
|
memset(&tv2, 0, sizeof(tv2)); |
371 |
|
|
|
372 |
|
|
diff_rev1 = rev1; |
373 |
|
|
diff_rev2 = rev2; |
374 |
|
|
path1 = path2 = NULL; |
375 |
|
|
|
376 |
|
|
rcsnum_tostr(rev1, rbuf1, sizeof(rbuf1)); |
377 |
|
|
if (!quiet) |
378 |
|
|
fprintf(stderr, "retrieving revision %s\n", rbuf1); |
379 |
|
|
|
380 |
|
|
if ((b1 = rcs_getrev(file, rev1)) == NULL) { |
381 |
|
|
warnx("failed to retrieve revision %s", rbuf1); |
382 |
|
|
goto out; |
383 |
|
|
} |
384 |
|
|
|
385 |
|
|
b1 = rcs_kwexp_buf(b1, file, rev1); |
386 |
|
|
tv[0].tv_sec = rcs_rev_getdate(file, rev1); |
387 |
|
|
tv[1].tv_sec = tv[0].tv_sec; |
388 |
|
|
|
389 |
|
|
rcsnum_tostr(rev2, rbuf2, sizeof(rbuf2)); |
390 |
|
|
if (!quiet) |
391 |
|
|
fprintf(stderr, "retrieving revision %s\n", rbuf2); |
392 |
|
|
|
393 |
|
|
if ((b2 = rcs_getrev(file, rev2)) == NULL) { |
394 |
|
|
warnx("failed to retrieve revision %s", rbuf2); |
395 |
|
|
goto out; |
396 |
|
|
} |
397 |
|
|
|
398 |
|
|
b2 = rcs_kwexp_buf(b2, file, rev2); |
399 |
|
|
tv2[0].tv_sec = rcs_rev_getdate(file, rev2); |
400 |
|
|
tv2[1].tv_sec = tv2[0].tv_sec; |
401 |
|
|
|
402 |
|
|
if (!quiet) |
403 |
|
|
fprintf(stderr, "%s -r%s -r%s\n", diffargs, rbuf1, rbuf2); |
404 |
|
|
|
405 |
|
|
(void)xasprintf(&path1, "%s/diff1.XXXXXXXXXX", rcs_tmpdir); |
406 |
|
|
buf_write_stmp(b1, path1); |
407 |
|
|
|
408 |
|
|
buf_free(b1); |
409 |
|
|
b1 = NULL; |
410 |
|
|
|
411 |
|
|
if (utimes(path1, (const struct timeval *)&tv) < 0) |
412 |
|
|
warn("utimes"); |
413 |
|
|
|
414 |
|
|
(void)xasprintf(&path2, "%s/diff2.XXXXXXXXXX", rcs_tmpdir); |
415 |
|
|
buf_write_stmp(b2, path2); |
416 |
|
|
|
417 |
|
|
buf_free(b2); |
418 |
|
|
b2 = NULL; |
419 |
|
|
|
420 |
|
|
if (utimes(path2, (const struct timeval *)&tv2) < 0) |
421 |
|
|
warn("utimes"); |
422 |
|
|
|
423 |
|
|
ret = diffreg(path1, path2, NULL, dflags); |
424 |
|
|
|
425 |
|
|
out: |
426 |
|
|
buf_free(b1); |
427 |
|
|
buf_free(b2); |
428 |
|
|
free(path1); |
429 |
|
|
free(path2); |
430 |
|
|
|
431 |
|
|
return (ret); |
432 |
|
|
} |
433 |
|
|
|
434 |
|
|
static void |
435 |
|
|
push_ignore_pats(char *pattern) |
436 |
|
|
{ |
437 |
|
|
size_t len; |
438 |
|
|
|
439 |
|
|
if (diff_ignore_pats == NULL) { |
440 |
|
|
len = strlen(pattern) + 1; |
441 |
|
|
diff_ignore_pats = xmalloc(len); |
442 |
|
|
strlcpy(diff_ignore_pats, pattern, len); |
443 |
|
|
} else { |
444 |
|
|
/* old + "|" + new + NUL */ |
445 |
|
|
len = strlen(diff_ignore_pats) + strlen(pattern) + 2; |
446 |
|
|
diff_ignore_pats = xreallocarray(diff_ignore_pats, len, 1); |
447 |
|
|
strlcat(diff_ignore_pats, "|", len); |
448 |
|
|
strlcat(diff_ignore_pats, pattern, len); |
449 |
|
|
} |
450 |
|
|
} |