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