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