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