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