1 /* Compare RCS revisions. */
2
3 /* Copyright 1982, 1988, 1989 Walter Tichy
4 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5 Distributed under license by the Free Software Foundation, Inc.
6
7 This file is part of RCS.
8
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 Report problems and direct all questions to:
25
26 rcs-bugs@cs.purdue.edu
27
28 */
29
30 /*
31 * $FreeBSD: src/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c,v 1.8 1999/08/27 23:36:55 peter Exp $
32 * $DragonFly: src/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c,v 1.2 2003/06/17 04:25:48 dillon Exp $
33 *
34 * Revision 5.19 1995/06/16 06:19:24 eggert
35 * Update FSF address.
36 *
37 * Revision 5.18 1995/06/01 16:23:43 eggert
38 * (main): Pass "--binary" if -kb and if --binary makes a difference.
39 * Don't treat + options specially.
40 *
41 * Revision 5.17 1994/03/17 14:05:48 eggert
42 * Specify subprocess input via file descriptor, not file name. Remove lint.
43 *
44 * Revision 5.16 1993/11/09 17:40:15 eggert
45 * -V now prints version on stdout and exits. Don't print usage twice.
46 *
47 * Revision 5.15 1993/11/03 17:42:27 eggert
48 * Add -z. Ignore -T. Pass -Vn to `co'. Add Name keyword.
49 * Put revision numbers in -c output. Improve quality of diagnostics.
50 *
51 * Revision 5.14 1992/07/28 16:12:44 eggert
52 * Add -V. Use co -M for better dates with traditional diff -c.
53 *
54 * Revision 5.13 1992/02/17 23:02:23 eggert
55 * Output more readable context diff headers.
56 * Suppress needless checkout and comparison of identical revisions.
57 *
58 * Revision 5.12 1992/01/24 18:44:19 eggert
59 * Add GNU diff 1.15.2's new options. lint -> RCS_lint
60 *
61 * Revision 5.11 1992/01/06 02:42:34 eggert
62 * Update usage string.
63 *
64 * Revision 5.10 1991/10/07 17:32:46 eggert
65 * Remove lint.
66 *
67 * Revision 5.9 1991/08/19 03:13:55 eggert
68 * Add RCSINIT, -r$. Tune.
69 *
70 * Revision 5.8 1991/04/21 11:58:21 eggert
71 * Add -x, RCSINIT, MS-DOS support.
72 *
73 * Revision 5.7 1990/12/13 06:54:07 eggert
74 * GNU diff 1.15 has -u.
75 *
76 * Revision 5.6 1990/11/01 05:03:39 eggert
77 * Remove unneeded setid check.
78 *
79 * Revision 5.5 1990/10/04 06:30:19 eggert
80 * Accumulate exit status across files.
81 *
82 * Revision 5.4 1990/09/27 01:31:43 eggert
83 * Yield 1, not EXIT_FAILURE, when diffs are found.
84 *
85 * Revision 5.3 1990/09/11 02:41:11 eggert
86 * Simplify -kkvl test.
87 *
88 * Revision 5.2 1990/09/04 17:07:19 eggert
89 * Diff's argv was too small by 1.
90 *
91 * Revision 5.1 1990/08/29 07:13:55 eggert
92 * Add -kkvl.
93 *
94 * Revision 5.0 1990/08/22 08:12:46 eggert
95 * Add -k, -V. Don't use access(). Add setuid support.
96 * Remove compile-time limits; use malloc instead.
97 * Don't pass arguments with leading '+' to diff; GNU DIFF treats them as options.
98 * Add GNU diff's flags. Make lock and temp files faster and safer.
99 * Ansify and Posixate.
100 *
101 * Revision 4.6 89/05/01 15:12:27 narten
102 * changed copyright header to reflect current distribution rules
103 *
104 * Revision 4.5 88/08/09 19:12:41 eggert
105 * Use execv(), not system(); yield exit status like diff(1)s; allow cc -R.
106 *
107 * Revision 4.4 87/12/18 11:37:46 narten
108 * changes Jay Lepreau made in the 4.3 BSD version, to add support for
109 * "-i", "-w", and "-t" flags and to permit flags to be bundled together,
110 * merged in.
111 *
112 * Revision 4.3 87/10/18 10:31:42 narten
113 * Updating version numbers. Changes relative to 1.1 actually
114 * relative to 4.1
115 *
116 * Revision 1.3 87/09/24 13:59:21 narten
117 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
118 * warnings)
119 *
120 * Revision 1.2 87/03/27 14:22:15 jenkins
121 * Port to suns
122 *
123 * Revision 4.1 83/05/03 22:13:19 wft
124 * Added default branch, option -q, exit status like diff.
125 * Added fterror() to replace faterror().
126 *
127 * Revision 3.6 83/01/15 17:52:40 wft
128 * Expanded mainprogram to handle multiple RCS files.
129 *
130 * Revision 3.5 83/01/06 09:33:45 wft
131 * Fixed passing of -c (context) option to diff.
132 *
133 * Revision 3.4 82/12/24 15:28:38 wft
134 * Added call to catchsig().
135 *
136 * Revision 3.3 82/12/10 16:08:17 wft
137 * Corrected checking of return code from diff; improved error msgs.
138 *
139 * Revision 3.2 82/12/04 13:20:09 wft
140 * replaced getdelta() with gettree(). Changed diagnostics.
141 *
142 * Revision 3.1 82/11/28 19:25:04 wft
143 * Initial revision.
144 *
145 */
146 #include "rcsbase.h"
147
148 #if DIFF_L
149 static char const *setup_label P((struct buf*,char const*,char const[datesize]));
150 #endif
151 static void cleanup P((void));
152
153 static int exitstatus;
154 static RILE *workptr;
155 static struct stat workstat;
156
157 mainProg(rcsdiffId, "rcsdiff", "$DragonFly: src/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c,v 1.2 2003/06/17 04:25:48 dillon Exp $")
158 {
159 static char const cmdusage[] =
160 "\nrcsdiff usage: rcsdiff -ksubst -q -rrev1 [-rrev2] -Vn -xsuff -zzone [diff options] file ...";
161
162 int revnums; /* counter for revision numbers given */
163 char const *rev1, *rev2; /* revision numbers from command line */
164 char const *xrev1, *xrev2; /* expanded revision numbers */
165 char const *expandarg, *lexpandarg, *suffixarg, *versionarg, *zonearg;
166 #if DIFF_L
167 static struct buf labelbuf[2];
168 int file_labels;
169 char const **diff_label1, **diff_label2;
170 char date2[datesize];
171 #endif
172 char const *cov[10 + !DIFF_L];
173 char const **diffv, **diffp, **diffpend; /* argv for subsidiary diff */
174 char const **pp, *p, *diffvstr;
175 struct buf commarg;
176 struct buf numericrev; /* expanded revision number */
177 struct hshentries *gendeltas; /* deltas to be generated */
178 struct hshentry * target;
179 char *a, *dcp, **newargv;
180 int no_diff_means_no_output;
181 register int c;
182
183 exitstatus = DIFF_SUCCESS;
184
185 bufautobegin(&commarg);
186 bufautobegin(&numericrev);
187 revnums = 0;
188 rev1 = rev2 = xrev2 = 0;
189 #if DIFF_L
190 file_labels = 0;
191 #endif
192 expandarg = suffixarg = versionarg = zonearg = 0;
193 no_diff_means_no_output = true;
194 suffixes = X_DEFAULT;
195
196 /*
197 * Room for runv extra + args [+ --binary] [+ 2 labels]
198 * + 1 file + 1 trailing null.
199 */
200 diffv = tnalloc(char const*, 1 + argc + !!OPEN_O_BINARY + 2*DIFF_L + 2);
201 diffp = diffv + 1;
202 *diffp++ = DIFF;
203
204 argc = getRCSINIT(argc, argv, &newargv);
205 argv = newargv;
206 while (a = *++argv, 0<--argc && *a++=='-') {
207 dcp = a;
208 while ((c = *a++)) switch (c) {
209 case 'r':
210 switch (++revnums) {
211 case 1: rev1=a; break;
212 case 2: rev2=a; break;
213 default: error("too many revision numbers");
214 }
215 goto option_handled;
216 case '-': case 'D':
217 no_diff_means_no_output = false;
218 /* fall into */
219 case 'C': case 'F': case 'I': case 'L': case 'W':
220 #if DIFF_L
221 if (c == 'L' && file_labels++ == 2)
222 faterror("too many -L options");
223 #endif
224 *dcp++ = c;
225 if (*a)
226 do *dcp++ = *a++;
227 while (*a);
228 else {
229 if (!--argc)
230 faterror("-%c needs following argument%s",
231 c, cmdusage
232 );
233 *diffp++ = *argv++;
234 }
235 break;
236 case 'y':
237 no_diff_means_no_output = false;
238 /* fall into */
239 case 'B': case 'H':
240 case '0': case '1': case '2': case '3': case '4':
241 case '5': case '6': case '7': case '8': case '9':
242 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
243 case 'h': case 'i': case 'n': case 'p':
244 case 't': case 'u': case 'w':
245 *dcp++ = c;
246 break;
247 case 'q':
248 quietflag=true;
249 break;
250 case 'x':
251 suffixarg = *argv;
252 suffixes = *argv + 2;
253 goto option_handled;
254 case 'z':
255 zonearg = *argv;
256 zone_set(*argv + 2);
257 goto option_handled;
258 case 'T':
259 /* Ignore -T, so that RCSINIT can contain -T. */
260 if (*a)
261 goto unknown;
262 break;
263 case 'V':
264 versionarg = *argv;
265 setRCSversion(versionarg);
266 goto option_handled;
267 case 'k':
268 expandarg = *argv;
269 if (0 <= str2expmode(expandarg+2))
270 goto option_handled;
271 /* fall into */
272 default:
273 unknown:
274 error("unknown option: %s%s", *argv, cmdusage);
275 };
276 option_handled:
277 if (dcp != *argv+1) {
278 *dcp = 0;
279 *diffp++ = *argv;
280 }
281 } /* end of option processing */
282
283 for (pp = diffv+2, c = 0; pp<diffp; )
284 c += strlen(*pp++) + 1;
285 diffvstr = a = tnalloc(char, c + 1);
286 for (pp = diffv+2; pp<diffp; ) {
287 p = *pp++;
288 *a++ = ' ';
289 while ((*a = *p++))
290 a++;
291 }
292 *a = 0;
293
294 #if DIFF_L
295 diff_label1 = diff_label2 = 0;
296 if (file_labels < 2) {
297 if (!file_labels)
298 diff_label1 = diffp++;
299 diff_label2 = diffp++;
300 }
301 #endif
302 diffpend = diffp;
303
304 cov[1] = CO;
305 cov[2] = "-q";
306 # if !DIFF_L
307 cov[3] = "-M";
308 # endif
309
310 /* Now handle all pathnames. */
311 if (nerror)
312 cleanup();
313 else if (argc < 1)
314 faterror("no input file%s", cmdusage);
315 else
316 for (; 0 < argc; cleanup(), ++argv, --argc) {
317 ffree();
318
319 if (pairnames(argc, argv, rcsreadopen, true, false) <= 0)
320 continue;
321 diagnose("===================================================================\nRCS file: %s\n",RCSname);
322 if (!rev2) {
323 /* Make sure work file is readable, and get its status. */
324 if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
325 eerror(workname);
326 continue;
327 }
328 }
329
330
331 gettree(); /* reads in the delta tree */
332
333 if (!Head) {
334 rcserror("no revisions present");
335 continue;
336 }
337 if (revnums==0 || !*rev1)
338 rev1 = Dbranch ? Dbranch : Head->num;
339
340 if (!fexpandsym(rev1, &numericrev, workptr)) continue;
341 if (!(target=genrevs(numericrev.string,(char *)0,(char *)0,(char *)0,&gendeltas))) continue;
342 xrev1=target->num;
343 #if DIFF_L
344 if (diff_label1)
345 *diff_label1 = setup_label(&labelbuf[0], target->num, target->date);
346 #endif
347
348 lexpandarg = expandarg;
349 if (revnums==2) {
350 if (!fexpandsym(
351 *rev2 ? rev2 : Dbranch ? Dbranch : Head->num,
352 &numericrev,
353 workptr
354 ))
355 continue;
356 if (!(target=genrevs(numericrev.string,(char *)0,(char *)0,(char *)0,&gendeltas))) continue;
357 xrev2=target->num;
358 if (no_diff_means_no_output && xrev1 == xrev2)
359 continue;
360 } else if (
361 target->lockedby
362 && !lexpandarg
363 && Expand == KEYVAL_EXPAND
364 && WORKMODE(RCSstat.st_mode,true) == workstat.st_mode
365 )
366 lexpandarg = "-kkvl";
367 Izclose(&workptr);
368 #if DIFF_L
369 if (diff_label2) {
370 if (revnums == 2)
371 *diff_label2 = setup_label(&labelbuf[1], target->num, target->date);
372 else {
373 time2date(workstat.st_mtime, date2);
374 *diff_label2 = setup_label(&labelbuf[1], (char*)0, date2);
375 }
376 }
377 #endif
378
379 diagnose("retrieving revision %s\n", xrev1);
380 bufscpy(&commarg, "-p");
381 bufscat(&commarg, rev1); /* not xrev1, for $Name's sake */
382
383 pp = &cov[3 + !DIFF_L];
384 *pp++ = commarg.string;
385 if (lexpandarg) *pp++ = lexpandarg;
386 if (suffixarg) *pp++ = suffixarg;
387 if (versionarg) *pp++ = versionarg;
388 if (zonearg) *pp++ = zonearg;
389 *pp++ = RCSname;
390 *pp = 0;
391
392 diffp = diffpend;
393 # if OPEN_O_BINARY
394 if (Expand == BINARY_EXPAND)
395 *diffp++ = "--binary";
396 # endif
397 diffp[0] = maketemp(0);
398 if (runv(-1, diffp[0], cov)) {
399 rcserror("co failed");
400 continue;
401 }
402 if (!rev2) {
403 diffp[1] = workname;
404 if (*workname == '-') {
405 char *dp = ftnalloc(char, strlen(workname)+3);
406 diffp[1] = dp;
407 *dp++ = '.';
408 *dp++ = SLASH;
409 VOID strcpy(dp, workname);
410 }
411 } else {
412 diagnose("retrieving revision %s\n",xrev2);
413 bufscpy(&commarg, "-p");
414 bufscat(&commarg, rev2); /* not xrev2, for $Name's sake */
415 cov[3 + !DIFF_L] = commarg.string;
416 diffp[1] = maketemp(1);
417 if (runv(-1, diffp[1], cov)) {
418 rcserror("co failed");
419 continue;
420 }
421 }
422 if (!rev2)
423 diagnose("diff%s -r%s %s\n", diffvstr, xrev1, workname);
424 else
425 diagnose("diff%s -r%s -r%s\n", diffvstr, xrev1, xrev2);
426
427 diffp[2] = 0;
428 switch (runv(-1, (char*)0, diffv)) {
429 case DIFF_SUCCESS:
430 break;
431 case DIFF_FAILURE:
432 if (exitstatus == DIFF_SUCCESS)
433 exitstatus = DIFF_FAILURE;
434 break;
435 default:
436 workerror("diff failed");
437 }
438 }
439
440 tempunlink();
441 exitmain(exitstatus);
442 }
443
444 static void
cleanup()445 cleanup()
446 {
447 if (nerror) exitstatus = DIFF_TROUBLE;
448 Izclose(&finptr);
449 Izclose(&workptr);
450 }
451
452 #if RCS_lint
453 # define exiterr rdiffExit
454 #endif
455 void
exiterr()456 exiterr()
457 {
458 tempunlink();
459 _exit(DIFF_TROUBLE);
460 }
461
462 #if DIFF_L
463 static char const *
setup_label(b,num,date)464 setup_label(b, num, date)
465 struct buf *b;
466 char const *num;
467 char const date[datesize];
468 {
469 char *p;
470 char datestr[datesize + zonelenmax];
471 VOID date2str(date, datestr);
472 bufalloc(b,
473 strlen(workname)
474 + sizeof datestr + 4
475 + (num ? strlen(num) : 0)
476 );
477 p = b->string;
478 if (num)
479 VOID sprintf(p, "-L%s\t%s\t%s", workname, datestr, num);
480 else
481 VOID sprintf(p, "-L%s\t%s", workname, datestr);
482 return p;
483 }
484 #endif
485