111894Speter/* Compare RCS revisions.  */
29Sjkh
311894Speter/* Copyright 1982, 1988, 1989 Walter Tichy
411894Speter   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
59Sjkh   Distributed under license by the Free Software Foundation, Inc.
69Sjkh
79SjkhThis file is part of RCS.
89Sjkh
99SjkhRCS is free software; you can redistribute it and/or modify
109Sjkhit under the terms of the GNU General Public License as published by
119Sjkhthe Free Software Foundation; either version 2, or (at your option)
129Sjkhany later version.
139Sjkh
149SjkhRCS is distributed in the hope that it will be useful,
159Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of
169SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
179SjkhGNU General Public License for more details.
189Sjkh
199SjkhYou should have received a copy of the GNU General Public License
2011894Speteralong with RCS; see the file COPYING.
2111894SpeterIf not, write to the Free Software Foundation,
2211894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
239Sjkh
249SjkhReport problems and direct all questions to:
259Sjkh
269Sjkh    rcs-bugs@cs.purdue.edu
279Sjkh
289Sjkh*/
299Sjkh
3011894Speter/*
3111894Speter * Revision 5.19  1995/06/16 06:19:24  eggert
3211894Speter * Update FSF address.
338858Srgrimes *
3411894Speter * Revision 5.18  1995/06/01 16:23:43  eggert
3511894Speter * (main): Pass "--binary" if -kb and if --binary makes a difference.
3611894Speter * Don't treat + options specially.
3711894Speter *
3811894Speter * Revision 5.17  1994/03/17 14:05:48  eggert
3911894Speter * Specify subprocess input via file descriptor, not file name.  Remove lint.
4011894Speter *
4111894Speter * Revision 5.16  1993/11/09 17:40:15  eggert
4211894Speter * -V now prints version on stdout and exits.  Don't print usage twice.
4311894Speter *
4411894Speter * Revision 5.15  1993/11/03 17:42:27  eggert
4511894Speter * Add -z.  Ignore -T.  Pass -Vn to `co'.  Add Name keyword.
4611894Speter * Put revision numbers in -c output.  Improve quality of diagnostics.
4711894Speter *
4811894Speter * Revision 5.14  1992/07/28  16:12:44  eggert
4911894Speter * Add -V.  Use co -M for better dates with traditional diff -c.
5011894Speter *
5111894Speter * Revision 5.13  1992/02/17  23:02:23  eggert
5211894Speter * Output more readable context diff headers.
5311894Speter * Suppress needless checkout and comparison of identical revisions.
5411894Speter *
5511894Speter * Revision 5.12  1992/01/24  18:44:19  eggert
5611894Speter * Add GNU diff 1.15.2's new options.  lint -> RCS_lint
5711894Speter *
5811894Speter * Revision 5.11  1992/01/06  02:42:34  eggert
5911894Speter * Update usage string.
6011894Speter *
619Sjkh * Revision 5.10  1991/10/07  17:32:46  eggert
629Sjkh * Remove lint.
639Sjkh *
649Sjkh * Revision 5.9  1991/08/19  03:13:55  eggert
659Sjkh * Add RCSINIT, -r$.  Tune.
669Sjkh *
679Sjkh * Revision 5.8  1991/04/21  11:58:21  eggert
689Sjkh * Add -x, RCSINIT, MS-DOS support.
699Sjkh *
709Sjkh * Revision 5.7  1990/12/13  06:54:07  eggert
719Sjkh * GNU diff 1.15 has -u.
729Sjkh *
739Sjkh * Revision 5.6  1990/11/01  05:03:39  eggert
749Sjkh * Remove unneeded setid check.
759Sjkh *
769Sjkh * Revision 5.5  1990/10/04  06:30:19  eggert
779Sjkh * Accumulate exit status across files.
789Sjkh *
799Sjkh * Revision 5.4  1990/09/27  01:31:43  eggert
809Sjkh * Yield 1, not EXIT_FAILURE, when diffs are found.
819Sjkh *
829Sjkh * Revision 5.3  1990/09/11  02:41:11  eggert
839Sjkh * Simplify -kkvl test.
849Sjkh *
859Sjkh * Revision 5.2  1990/09/04  17:07:19  eggert
869Sjkh * Diff's argv was too small by 1.
879Sjkh *
889Sjkh * Revision 5.1  1990/08/29  07:13:55  eggert
899Sjkh * Add -kkvl.
909Sjkh *
919Sjkh * Revision 5.0  1990/08/22  08:12:46  eggert
929Sjkh * Add -k, -V.  Don't use access().  Add setuid support.
939Sjkh * Remove compile-time limits; use malloc instead.
949Sjkh * Don't pass arguments with leading '+' to diff; GNU DIFF treats them as options.
959Sjkh * Add GNU diff's flags.  Make lock and temp files faster and safer.
969Sjkh * Ansify and Posixate.
979Sjkh *
989Sjkh * Revision 4.6  89/05/01  15:12:27  narten
999Sjkh * changed copyright header to reflect current distribution rules
1008858Srgrimes *
1019Sjkh * Revision 4.5  88/08/09  19:12:41  eggert
1029Sjkh * Use execv(), not system(); yield exit status like diff(1)s; allow cc -R.
1038858Srgrimes *
1049Sjkh * Revision 4.4  87/12/18  11:37:46  narten
1059Sjkh * changes Jay Lepreau made in the 4.3 BSD version, to add support for
1069Sjkh * "-i", "-w", and "-t" flags and to permit flags to be bundled together,
1079Sjkh * merged in.
1088858Srgrimes *
1099Sjkh * Revision 4.3  87/10/18  10:31:42  narten
1109Sjkh * Updating version numbers. Changes relative to 1.1 actually
1119Sjkh * relative to 4.1
1128858Srgrimes *
1139Sjkh * Revision 1.3  87/09/24  13:59:21  narten
1148858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1159Sjkh * warnings)
1168858Srgrimes *
1179Sjkh * Revision 1.2  87/03/27  14:22:15  jenkins
1189Sjkh * Port to suns
1198858Srgrimes *
1209Sjkh * Revision 4.1  83/05/03  22:13:19  wft
1219Sjkh * Added default branch, option -q, exit status like diff.
1229Sjkh * Added fterror() to replace faterror().
1238858Srgrimes *
1249Sjkh * Revision 3.6  83/01/15  17:52:40  wft
1259Sjkh * Expanded mainprogram to handle multiple RCS files.
1269Sjkh *
1279Sjkh * Revision 3.5  83/01/06  09:33:45  wft
1289Sjkh * Fixed passing of -c (context) option to diff.
1299Sjkh *
1309Sjkh * Revision 3.4  82/12/24  15:28:38  wft
1319Sjkh * Added call to catchsig().
1329Sjkh *
1339Sjkh * Revision 3.3  82/12/10  16:08:17  wft
1349Sjkh * Corrected checking of return code from diff; improved error msgs.
1359Sjkh *
1369Sjkh * Revision 3.2  82/12/04  13:20:09  wft
1379Sjkh * replaced getdelta() with gettree(). Changed diagnostics.
1389Sjkh *
1399Sjkh * Revision 3.1  82/11/28  19:25:04  wft
1409Sjkh * Initial revision.
1419Sjkh *
1429Sjkh */
1439Sjkh#include "rcsbase.h"
1449Sjkh
1459Sjkh#if DIFF_L
1469Sjkhstatic char const *setup_label P((struct buf*,char const*,char const[datesize]));
1479Sjkh#endif
1489Sjkhstatic void cleanup P((void));
1499Sjkh
1509Sjkhstatic int exitstatus;
1519Sjkhstatic RILE *workptr;
1529Sjkhstatic struct stat workstat;
1539Sjkh
15450472SpetermainProg(rcsdiffId, "rcsdiff", "$FreeBSD: releng/11.0/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c 50472 1999-08-27 23:37:10Z peter $")
1559Sjkh{
1569Sjkh    static char const cmdusage[] =
15711894Speter	    "\nrcsdiff usage: rcsdiff -ksubst -q -rrev1 [-rrev2] -Vn -xsuff -zzone [diff options] file ...";
1589Sjkh
1599Sjkh    int  revnums;                 /* counter for revision numbers given */
1609Sjkh    char const *rev1, *rev2;	/* revision numbers from command line */
1619Sjkh    char const *xrev1, *xrev2;	/* expanded revision numbers */
16211894Speter    char const *expandarg, *lexpandarg, *suffixarg, *versionarg, *zonearg;
1639Sjkh#if DIFF_L
1649Sjkh    static struct buf labelbuf[2];
1659Sjkh    int file_labels;
1669Sjkh    char const **diff_label1, **diff_label2;
1679Sjkh    char date2[datesize];
1689Sjkh#endif
16911894Speter    char const *cov[10 + !DIFF_L];
17011894Speter    char const **diffv, **diffp, **diffpend;	/* argv for subsidiary diff */
1719Sjkh    char const **pp, *p, *diffvstr;
1729Sjkh    struct buf commarg;
1739Sjkh    struct buf numericrev;	/* expanded revision number */
1749Sjkh    struct hshentries *gendeltas;	/* deltas to be generated */
1759Sjkh    struct hshentry * target;
1769Sjkh    char *a, *dcp, **newargv;
17711894Speter    int no_diff_means_no_output;
1789Sjkh    register c;
1799Sjkh
1809Sjkh    exitstatus = DIFF_SUCCESS;
1819Sjkh
1829Sjkh    bufautobegin(&commarg);
1839Sjkh    bufautobegin(&numericrev);
1849Sjkh    revnums = 0;
18511894Speter    rev1 = rev2 = xrev2 = 0;
1869Sjkh#if DIFF_L
1879Sjkh    file_labels = 0;
1889Sjkh#endif
18911894Speter    expandarg = suffixarg = versionarg = zonearg = 0;
19011894Speter    no_diff_means_no_output = true;
1919Sjkh    suffixes = X_DEFAULT;
1929Sjkh
19311894Speter    /*
19411894Speter    * Room for runv extra + args [+ --binary] [+ 2 labels]
19511894Speter    * + 1 file + 1 trailing null.
19611894Speter    */
19711894Speter    diffv = tnalloc(char const*, 1 + argc + !!OPEN_O_BINARY + 2*DIFF_L + 2);
19811894Speter    diffp = diffv + 1;
1999Sjkh    *diffp++ = DIFF;
2009Sjkh
2019Sjkh    argc = getRCSINIT(argc, argv, &newargv);
2029Sjkh    argv = newargv;
2039Sjkh    while (a = *++argv,  0<--argc && *a++=='-') {
2049Sjkh	dcp = a;
20511894Speter	while ((c = *a++)) switch (c) {
2069Sjkh	    case 'r':
2079Sjkh		    switch (++revnums) {
2089Sjkh			case 1: rev1=a; break;
2099Sjkh			case 2: rev2=a; break;
21011894Speter			default: error("too many revision numbers");
2119Sjkh		    }
2129Sjkh		    goto option_handled;
21311894Speter	    case '-': case 'D':
21411894Speter		    no_diff_means_no_output = false;
21511894Speter		    /* fall into */
21611894Speter	    case 'C': case 'F': case 'I': case 'L': case 'W':
2179Sjkh#if DIFF_L
21813512Smpp		    if (c == 'L'  &&  file_labels++ == 2)
2199Sjkh			faterror("too many -L options");
2209Sjkh#endif
2219Sjkh		    *dcp++ = c;
2229Sjkh		    if (*a)
2239Sjkh			do *dcp++ = *a++;
2249Sjkh			while (*a);
2259Sjkh		    else {
2269Sjkh			if (!--argc)
2279Sjkh			    faterror("-%c needs following argument%s",
2289Sjkh				    c, cmdusage
2299Sjkh			    );
2309Sjkh			*diffp++ = *argv++;
2319Sjkh		    }
2329Sjkh		    break;
23311894Speter	    case 'y':
23411894Speter		    no_diff_means_no_output = false;
23511894Speter		    /* fall into */
23611894Speter	    case 'B': case 'H':
2379Sjkh	    case '0': case '1': case '2': case '3': case '4':
2389Sjkh	    case '5': case '6': case '7': case '8': case '9':
2399Sjkh	    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
2409Sjkh	    case 'h': case 'i': case 'n': case 'p':
2419Sjkh	    case 't': case 'u': case 'w':
2429Sjkh		    *dcp++ = c;
2439Sjkh		    break;
2449Sjkh	    case 'q':
2459Sjkh		    quietflag=true;
2469Sjkh		    break;
2479Sjkh	    case 'x':
24811894Speter		    suffixarg = *argv;
2499Sjkh		    suffixes = *argv + 2;
2509Sjkh		    goto option_handled;
25111894Speter	    case 'z':
25211894Speter		    zonearg = *argv;
25311894Speter		    zone_set(*argv + 2);
25411894Speter		    goto option_handled;
25511894Speter	    case 'T':
25611894Speter		    /* Ignore -T, so that RCSINIT can contain -T.  */
25711894Speter		    if (*a)
25811894Speter			    goto unknown;
25911894Speter		    break;
2609Sjkh	    case 'V':
2619Sjkh		    versionarg = *argv;
2629Sjkh		    setRCSversion(versionarg);
2639Sjkh		    goto option_handled;
2649Sjkh	    case 'k':
2659Sjkh		    expandarg = *argv;
2669Sjkh		    if (0 <= str2expmode(expandarg+2))
2679Sjkh			goto option_handled;
2689Sjkh		    /* fall into */
2699Sjkh	    default:
27011894Speter	    unknown:
27111894Speter		    error("unknown option: %s%s", *argv, cmdusage);
2729Sjkh	    };
2739Sjkh      option_handled:
2749Sjkh	if (dcp != *argv+1) {
2759Sjkh	    *dcp = 0;
2769Sjkh	    *diffp++ = *argv;
2779Sjkh	}
2789Sjkh    } /* end of option processing */
2799Sjkh
28011894Speter    for (pp = diffv+2, c = 0;  pp<diffp;  )
2819Sjkh	    c += strlen(*pp++) + 1;
2829Sjkh    diffvstr = a = tnalloc(char, c + 1);
28311894Speter    for (pp = diffv+2;  pp<diffp;  ) {
2849Sjkh	    p = *pp++;
2859Sjkh	    *a++ = ' ';
2869Sjkh	    while ((*a = *p++))
2879Sjkh		    a++;
2889Sjkh    }
2899Sjkh    *a = 0;
2909Sjkh
2919Sjkh#if DIFF_L
29211894Speter    diff_label1 = diff_label2 = 0;
2939Sjkh    if (file_labels < 2) {
2949Sjkh	    if (!file_labels)
2959Sjkh		    diff_label1 = diffp++;
2969Sjkh	    diff_label2 = diffp++;
2979Sjkh    }
2989Sjkh#endif
29911894Speter    diffpend = diffp;
3009Sjkh
30111894Speter    cov[1] = CO;
30211894Speter    cov[2] = "-q";
30311894Speter#   if !DIFF_L
30411894Speter	cov[3] = "-M";
30511894Speter#   endif
3069Sjkh
30711894Speter    /* Now handle all pathnames.  */
30811894Speter    if (nerror)
30911894Speter	cleanup();
31011894Speter    else if (argc < 1)
31111894Speter	faterror("no input file%s", cmdusage);
31211894Speter    else
31311894Speter	for (;  0 < argc;  cleanup(), ++argv, --argc) {
3149Sjkh	    ffree();
3159Sjkh
31611894Speter	    if (pairnames(argc, argv, rcsreadopen, true, false)  <=  0)
3179Sjkh		    continue;
31811894Speter	    diagnose("===================================================================\nRCS file: %s\n",RCSname);
3199Sjkh	    if (!rev2) {
3209Sjkh		/* Make sure work file is readable, and get its status.  */
32111894Speter		if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
32211894Speter		    eerror(workname);
3239Sjkh		    continue;
3249Sjkh		}
3259Sjkh	    }
3269Sjkh
3279Sjkh
3289Sjkh	    gettree(); /* reads in the delta tree */
3299Sjkh
33011894Speter	    if (!Head) {
33111894Speter		    rcserror("no revisions present");
3329Sjkh		    continue;
3339Sjkh	    }
3349Sjkh	    if (revnums==0  ||  !*rev1)
3359Sjkh		    rev1  =  Dbranch ? Dbranch : Head->num;
3369Sjkh
3379Sjkh	    if (!fexpandsym(rev1, &numericrev, workptr)) continue;
33811894Speter	    if (!(target=genrevs(numericrev.string,(char *)0,(char *)0,(char *)0,&gendeltas))) continue;
3399Sjkh	    xrev1=target->num;
3409Sjkh#if DIFF_L
3419Sjkh	    if (diff_label1)
3429Sjkh		*diff_label1 = setup_label(&labelbuf[0], target->num, target->date);
3439Sjkh#endif
3449Sjkh
3459Sjkh	    lexpandarg = expandarg;
3469Sjkh	    if (revnums==2) {
3479Sjkh		    if (!fexpandsym(
3489Sjkh			    *rev2 ? rev2  : Dbranch ? Dbranch  : Head->num,
3499Sjkh			    &numericrev,
3509Sjkh			    workptr
3519Sjkh		    ))
3529Sjkh			continue;
35311894Speter		    if (!(target=genrevs(numericrev.string,(char *)0,(char *)0,(char *)0,&gendeltas))) continue;
3549Sjkh		    xrev2=target->num;
35511894Speter		    if (no_diff_means_no_output  &&  xrev1 == xrev2)
35611894Speter			continue;
3579Sjkh	    } else if (
3589Sjkh			target->lockedby
3599Sjkh		&&	!lexpandarg
3609Sjkh		&&	Expand == KEYVAL_EXPAND
3619Sjkh		&&	WORKMODE(RCSstat.st_mode,true) == workstat.st_mode
3629Sjkh	    )
3639Sjkh		    lexpandarg = "-kkvl";
3649Sjkh	    Izclose(&workptr);
3659Sjkh#if DIFF_L
3669Sjkh	    if (diff_label2)
3679Sjkh		if (revnums == 2)
3689Sjkh		    *diff_label2 = setup_label(&labelbuf[1], target->num, target->date);
3699Sjkh		else {
3709Sjkh		    time2date(workstat.st_mtime, date2);
37111894Speter		    *diff_label2 = setup_label(&labelbuf[1], (char*)0, date2);
3729Sjkh		}
3739Sjkh#endif
3749Sjkh
3759Sjkh	    diagnose("retrieving revision %s\n", xrev1);
3769Sjkh	    bufscpy(&commarg, "-p");
37711894Speter	    bufscat(&commarg, rev1); /* not xrev1, for $Name's sake */
3789Sjkh
37911894Speter	    pp = &cov[3 + !DIFF_L];
3809Sjkh	    *pp++ = commarg.string;
38111894Speter	    if (lexpandarg) *pp++ = lexpandarg;
38211894Speter	    if (suffixarg) *pp++ = suffixarg;
38311894Speter	    if (versionarg) *pp++ = versionarg;
38411894Speter	    if (zonearg) *pp++ = zonearg;
38511894Speter	    *pp++ = RCSname;
3869Sjkh	    *pp = 0;
3879Sjkh
38811894Speter	    diffp = diffpend;
38911894Speter#	    if OPEN_O_BINARY
39011894Speter		    if (Expand == BINARY_EXPAND)
39111894Speter			    *diffp++ = "--binary";
39211894Speter#	    endif
39311894Speter	    diffp[0] = maketemp(0);
39411894Speter	    if (runv(-1, diffp[0], cov)) {
39511894Speter		    rcserror("co failed");
3969Sjkh		    continue;
3979Sjkh	    }
3989Sjkh	    if (!rev2) {
39911894Speter		    diffp[1] = workname;
40011894Speter		    if (*workname == '-') {
40111894Speter			char *dp = ftnalloc(char, strlen(workname)+3);
4029Sjkh			diffp[1] = dp;
4039Sjkh			*dp++ = '.';
4049Sjkh			*dp++ = SLASH;
40511894Speter			VOID strcpy(dp, workname);
4069Sjkh		    }
4079Sjkh	    } else {
4089Sjkh		    diagnose("retrieving revision %s\n",xrev2);
4099Sjkh		    bufscpy(&commarg, "-p");
41011894Speter		    bufscat(&commarg, rev2); /* not xrev2, for $Name's sake */
41111894Speter		    cov[3 + !DIFF_L] = commarg.string;
41211894Speter		    diffp[1] = maketemp(1);
41311894Speter		    if (runv(-1, diffp[1], cov)) {
41411894Speter			    rcserror("co failed");
4159Sjkh			    continue;
4169Sjkh		    }
4179Sjkh	    }
4189Sjkh	    if (!rev2)
41911894Speter		    diagnose("diff%s -r%s %s\n", diffvstr, xrev1, workname);
4209Sjkh	    else
4219Sjkh		    diagnose("diff%s -r%s -r%s\n", diffvstr, xrev1, xrev2);
4229Sjkh
42311894Speter	    diffp[2] = 0;
42411894Speter	    switch (runv(-1, (char*)0, diffv)) {
4259Sjkh		    case DIFF_SUCCESS:
4269Sjkh			    break;
4279Sjkh		    case DIFF_FAILURE:
4289Sjkh			    if (exitstatus == DIFF_SUCCESS)
4299Sjkh				    exitstatus = DIFF_FAILURE;
4309Sjkh			    break;
4319Sjkh		    default:
43211894Speter			    workerror("diff failed");
4339Sjkh	    }
43411894Speter	}
4359Sjkh
4369Sjkh    tempunlink();
4379Sjkh    exitmain(exitstatus);
4389Sjkh}
4399Sjkh
4409Sjkh    static void
4419Sjkhcleanup()
4429Sjkh{
4439Sjkh    if (nerror) exitstatus = DIFF_TROUBLE;
4449Sjkh    Izclose(&finptr);
4459Sjkh    Izclose(&workptr);
4469Sjkh}
4479Sjkh
44811894Speter#if RCS_lint
4499Sjkh#	define exiterr rdiffExit
4509Sjkh#endif
45111894Speter    void
4529Sjkhexiterr()
4539Sjkh{
4549Sjkh    tempunlink();
4559Sjkh    _exit(DIFF_TROUBLE);
4569Sjkh}
4579Sjkh
4589Sjkh#if DIFF_L
4599Sjkh	static char const *
46011894Spetersetup_label(b, num, date)
4619Sjkh	struct buf *b;
46211894Speter	char const *num;
4639Sjkh	char const date[datesize];
4649Sjkh{
4659Sjkh	char *p;
46611894Speter	char datestr[datesize + zonelenmax];
46711894Speter	VOID date2str(date, datestr);
46811894Speter	bufalloc(b,
46911894Speter		strlen(workname)
47011894Speter		+ sizeof datestr + 4
47111894Speter		+ (num ? strlen(num) : 0)
47211894Speter	);
4739Sjkh	p = b->string;
47411894Speter	if (num)
47511894Speter		VOID sprintf(p, "-L%s\t%s\t%s", workname, datestr, num);
47611894Speter	else
47711894Speter		VOID sprintf(p, "-L%s\t%s", workname, datestr);
4789Sjkh	return p;
4799Sjkh}
4809Sjkh#endif
481