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