1/* Generate 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: rcsgen.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.16 1995/06/16 06:19:24 eggert 36 * Update FSF address. 37 * 38 * Revision 5.15 1995/06/01 16:23:43 eggert 39 * (putadmin): Open RCS file with FOPEN_WB. 40 * 41 * Revision 5.14 1994/03/17 14:05:48 eggert 42 * Work around SVR4 stdio performance bug. 43 * Flush stderr after prompt. Remove lint. 44 * 45 * Revision 5.13 1993/11/03 17:42:27 eggert 46 * Don't discard ignored phrases. Improve quality of diagnostics. 47 * 48 * Revision 5.12 1992/07/28 16:12:44 eggert 49 * Statement macro names now end in _. 50 * Be consistent about pathnames vs filenames. 51 * 52 * Revision 5.11 1992/01/24 18:44:19 eggert 53 * Move put routines here from rcssyn.c. 54 * Add support for bad_creat0. 55 * 56 * Revision 5.10 1991/10/07 17:32:46 eggert 57 * Fix log bugs, e.g. ci -t/dev/null when has_mmap. 58 * 59 * Revision 5.9 1991/09/10 22:15:46 eggert 60 * Fix test for redirected stdin. 61 * 62 * Revision 5.8 1991/08/19 03:13:55 eggert 63 * Add piece tables. Tune. 64 * 65 * Revision 5.7 1991/04/21 11:58:24 eggert 66 * Add MS-DOS support. 67 * 68 * Revision 5.6 1990/12/27 19:54:26 eggert 69 * Fix bug: rcs -t inserted \n, making RCS file grow. 70 * 71 * Revision 5.5 1990/12/04 05:18:45 eggert 72 * Use -I for prompts and -q for diagnostics. 73 * 74 * Revision 5.4 1990/11/01 05:03:47 eggert 75 * Add -I and new -t behavior. Permit arbitrary data in logs. 76 * 77 * Revision 5.3 1990/09/21 06:12:43 hammer 78 * made putdesc() treat stdin the same whether or not it was from a terminal 79 * by making it recognize that a single '.' was then end of the 80 * description always 81 * 82 * Revision 5.2 1990/09/04 08:02:25 eggert 83 * Fix `co -p1.1 -ko' bug. Standardize yes-or-no procedure. 84 * 85 * Revision 5.1 1990/08/29 07:14:01 eggert 86 * Clean old log messages too. 87 * 88 * Revision 5.0 1990/08/22 08:12:52 eggert 89 * Remove compile-time limits; use malloc instead. 90 * Ansify and Posixate. 91 * 92 * Revision 4.7 89/05/01 15:12:49 narten 93 * changed copyright header to reflect current distribution rules 94 * 95 * Revision 4.6 88/08/28 14:59:10 eggert 96 * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin() 97 * 98 * Revision 4.5 87/12/18 11:43:25 narten 99 * additional lint cleanups, and a bug fix from the 4.3BSD version that 100 * keeps "ci" from sticking a '\377' into the description if you run it 101 * with a zero-length file as the description. (Guy Harris) 102 * 103 * Revision 4.4 87/10/18 10:35:10 narten 104 * Updating version numbers. Changes relative to 1.1 actually relative to 105 * 4.2 106 * 107 * Revision 1.3 87/09/24 13:59:51 narten 108 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 109 * warnings) 110 * 111 * Revision 1.2 87/03/27 14:22:27 jenkins 112 * Port to suns 113 * 114 * Revision 4.2 83/12/02 23:01:39 wft 115 * merged 4.1 and 3.3.1.1 (clearerr(stdin)). 116 * 117 * Revision 4.1 83/05/10 16:03:33 wft 118 * Changed putamin() to abort if trying to reread redirected stdin. 119 * Fixed getdesc() to output a prompt on initial newline. 120 * 121 * Revision 3.3.1.1 83/10/19 04:21:51 lepreau 122 * Added clearerr(stdin) for re-reading description from stdin. 123 * 124 * Revision 3.3 82/11/28 21:36:49 wft 125 * 4.2 prerelease 126 * 127 * Revision 3.3 82/11/28 21:36:49 wft 128 * Replaced ferror() followed by fclose() with ffclose(). 129 * Putdesc() now suppresses the prompts if stdin 130 * is not a terminal. A pointer to the current log message is now 131 * inserted into the corresponding delta, rather than leaving it in a 132 * global variable. 133 * 134 * Revision 3.2 82/10/18 21:11:26 wft 135 * I added checks for write errors during editing, and improved 136 * the prompt on putdesc(). 137 * 138 * Revision 3.1 82/10/13 15:55:09 wft 139 * corrected type of variables assigned to by getc (char --> int) 140 */ 141 142 143 144 145#include "rcsbase.h" 146 147libId(genId, "$Id: rcsgen.c 3476 2003-06-11 15:56:10Z darkwyrm $") 148 149int interactiveflag; /* Should we act as if stdin is a tty? */ 150struct buf curlogbuf; /* buffer for current log message */ 151 152enum stringwork { enter, copy, edit, expand, edit_expand }; 153 154static void putdelta P((struct hshentry const*,FILE*)); 155static void scandeltatext P((struct hshentry*,enum stringwork,int)); 156 157 158 159 160 char const * 161buildrevision(deltas, target, outfile, expandflag) 162 struct hshentries const *deltas; 163 struct hshentry *target; 164 FILE *outfile; 165 int expandflag; 166/* Function: Generates the revision given by target 167 * by retrieving all deltas given by parameter deltas and combining them. 168 * If outfile is set, the revision is output to it, 169 * otherwise written into a temporary file. 170 * Temporary files are allocated by maketemp(). 171 * if expandflag is set, keyword expansion is performed. 172 * Return 0 if outfile is set, the name of the temporary file otherwise. 173 * 174 * Algorithm: Copy initial revision unchanged. Then edit all revisions but 175 * the last one into it, alternating input and output files (resultname and 176 * editname). The last revision is then edited in, performing simultaneous 177 * keyword substitution (this saves one extra pass). 178 * All this simplifies if only one revision needs to be generated, 179 * or no keyword expansion is necessary, or if output goes to stdout. 180 */ 181{ 182 if (deltas->first == target) { 183 /* only latest revision to generate */ 184 openfcopy(outfile); 185 scandeltatext(target, expandflag?expand:copy, true); 186 if (outfile) 187 return 0; 188 else { 189 Ozclose(&fcopy); 190 return resultname; 191 } 192 } else { 193 /* several revisions to generate */ 194 /* Get initial revision without keyword expansion. */ 195 scandeltatext(deltas->first, enter, false); 196 while ((deltas=deltas->rest)->rest) { 197 /* do all deltas except last one */ 198 scandeltatext(deltas->first, edit, false); 199 } 200 if (expandflag || outfile) { 201 /* first, get to beginning of file*/ 202 finishedit((struct hshentry*)0, outfile, false); 203 } 204 scandeltatext(target, expandflag?edit_expand:edit, true); 205 finishedit( 206 expandflag ? target : (struct hshentry*)0, 207 outfile, true 208 ); 209 if (outfile) 210 return 0; 211 Ozclose(&fcopy); 212 return resultname; 213 } 214} 215 216 217 218 static void 219scandeltatext(delta, func, needlog) 220 struct hshentry *delta; 221 enum stringwork func; 222 int needlog; 223/* Function: Scans delta text nodes up to and including the one given 224 * by delta. For the one given by delta, the log message is saved into 225 * delta->log if needlog is set; func specifies how to handle the text. 226 * Similarly, if needlog, delta->igtext is set to the ignored phrases. 227 * Assumes the initial lexeme must be read in first. 228 * Does not advance nexttok after it is finished. 229 */ 230{ 231 struct hshentry const *nextdelta; 232 struct cbuf cb; 233 234 for (;;) { 235 if (eoflex()) 236 fatserror("can't find delta for revision %s", delta->num); 237 nextlex(); 238 if (!(nextdelta=getnum())) { 239 fatserror("delta number corrupted"); 240 } 241 getkeystring(Klog); 242 if (needlog && delta==nextdelta) { 243 cb = savestring(&curlogbuf); 244 delta->log = cleanlogmsg(curlogbuf.string, cb.size); 245 nextlex(); 246 delta->igtext = getphrases(Ktext); 247 } else {readstring(); 248 ignorephrases(Ktext); 249 } 250 getkeystring(Ktext); 251 252 if (delta==nextdelta) 253 break; 254 readstring(); /* skip over it */ 255 256 } 257 switch (func) { 258 case enter: enterstring(); break; 259 case copy: copystring(); break; 260 case expand: xpandstring(delta); break; 261 case edit: editstring((struct hshentry *)0); break; 262 case edit_expand: editstring(delta); break; 263 } 264} 265 266 struct cbuf 267cleanlogmsg(m, s) 268 char *m; 269 size_t s; 270{ 271 register char *t = m; 272 register char const *f = t; 273 struct cbuf r; 274 while (s) { 275 --s; 276 if ((*t++ = *f++) == '\n') 277 while (m < --t) 278 if (t[-1]!=' ' && t[-1]!='\t') { 279 *t++ = '\n'; 280 break; 281 } 282 } 283 while (m < t && (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n')) 284 --t; 285 r.string = m; 286 r.size = t - m; 287 return r; 288} 289 290 291int ttystdin() 292{ 293 static int initialized; 294 if (!initialized) { 295 if (!interactiveflag) 296 interactiveflag = isatty(STDIN_FILENO); 297 initialized = true; 298 } 299 return interactiveflag; 300} 301 302 int 303getcstdin() 304{ 305 register FILE *in; 306 register int c; 307 308 in = stdin; 309 if (feof(in) && ttystdin()) 310 clearerr(in); 311 c = getc(in); 312 if (c == EOF) { 313 testIerror(in); 314 if (feof(in) && ttystdin()) 315 afputc('\n',stderr); 316 } 317 return c; 318} 319 320#if has_prototypes 321 int 322yesorno(int default_answer, char const *question, ...) 323#else 324 /*VARARGS2*/ int 325 yesorno(default_answer, question, va_alist) 326 int default_answer; char const *question; va_dcl 327#endif 328{ 329 va_list args; 330 register int c, r; 331 if (!quietflag && ttystdin()) { 332 oflush(); 333 vararg_start(args, question); 334 fvfprintf(stderr, question, args); 335 va_end(args); 336 eflush(); 337 r = c = getcstdin(); 338 while (c!='\n' && !feof(stdin)) 339 c = getcstdin(); 340 if (r=='y' || r=='Y') 341 return true; 342 if (r=='n' || r=='N') 343 return false; 344 } 345 return default_answer; 346} 347 348 349 void 350putdesc(textflag, textfile) 351 int textflag; 352 char *textfile; 353/* Function: puts the descriptive text into file frewrite. 354 * if finptr && !textflag, the text is copied from the old description. 355 * Otherwise, if textfile, the text is read from that 356 * file, or from stdin, if !textfile. 357 * A textfile with a leading '-' is treated as a string, not a pathname. 358 * If finptr, the old descriptive text is discarded. 359 * Always clears foutptr. 360 */ 361{ 362 static struct buf desc; 363 static struct cbuf desclean; 364 365 register FILE *txt; 366 register int c; 367 register FILE * frew; 368 register char *p; 369 register size_t s; 370 char const *plim; 371 372 frew = frewrite; 373 if (finptr && !textflag) { 374 /* copy old description */ 375 aprintf(frew, "\n\n%s%c", Kdesc, nextc); 376 foutptr = frewrite; 377 getdesc(false); 378 foutptr = 0; 379 } else { 380 foutptr = 0; 381 /* get new description */ 382 if (finptr) { 383 /*skip old description*/ 384 getdesc(false); 385 } 386 aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM); 387 if (!textfile) 388 desclean = getsstdin( 389 "t-", "description", 390 "NOTE: This is NOT the log message!\n", &desc 391 ); 392 else if (!desclean.string) { 393 if (*textfile == '-') { 394 p = textfile + 1; 395 s = strlen(p); 396 } else { 397 if (!(txt = fopenSafer(textfile, "r"))) 398 efaterror(textfile); 399 bufalloc(&desc, 1); 400 p = desc.string; 401 plim = p + desc.size; 402 for (;;) { 403 if ((c=getc(txt)) == EOF) { 404 testIerror(txt); 405 if (feof(txt)) 406 break; 407 } 408 if (plim <= p) 409 p = bufenlarge(&desc, &plim); 410 *p++ = c; 411 } 412 if (fclose(txt) != 0) 413 Ierror(); 414 s = p - desc.string; 415 p = desc.string; 416 } 417 desclean = cleanlogmsg(p, s); 418 } 419 putstring(frew, false, desclean, true); 420 aputc_('\n', frew) 421 } 422} 423 424 struct cbuf 425getsstdin(option, name, note, buf) 426 char const *option, *name, *note; 427 struct buf *buf; 428{ 429 register int c; 430 register char *p; 431 register size_t i; 432 register int tty = ttystdin(); 433 434 if (tty) { 435 aprintf(stderr, 436 "enter %s, terminated with single '.' or end of file:\n%s>> ", 437 name, note 438 ); 439 eflush(); 440 } else if (feof(stdin)) 441 rcsfaterror("can't reread redirected stdin for %s; use -%s<%s>", 442 name, option, name 443 ); 444 445 for ( 446 i = 0, p = 0; 447 c = getcstdin(), !feof(stdin); 448 bufrealloc(buf, i+1), p = buf->string, p[i++] = c 449 ) 450 if (c == '\n') 451 if (i && p[i-1]=='.' && (i==1 || p[i-2]=='\n')) { 452 /* Remove trailing '.'. */ 453 --i; 454 break; 455 } else if (tty) { 456 aputs(">> ", stderr); 457 eflush(); 458 } 459 return cleanlogmsg(p, i); 460} 461 462 463 void 464putadmin() 465/* Output the admin node. */ 466{ 467 register FILE *fout; 468 struct assoc const *curassoc; 469 struct rcslock const *curlock; 470 struct access const *curaccess; 471 472 if (!(fout = frewrite)) { 473# if bad_creat0 474 ORCSclose(); 475 fout = fopenSafer(makedirtemp(0), FOPEN_WB); 476# else 477 int fo = fdlock; 478 fdlock = -1; 479 fout = fdopen(fo, FOPEN_WB); 480# endif 481 482 if (!(frewrite = fout)) 483 efaterror(RCSname); 484 } 485 486 /* 487 * Output the first character with putc, not printf. 488 * Otherwise, an SVR4 stdio bug buffers output inefficiently. 489 */ 490 aputc_(*Khead, fout) 491 aprintf(fout, "%s\t%s;\n", Khead + 1, Head?Head->num:""); 492 if (Dbranch && VERSION(4)<=RCSversion) 493 aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch); 494 495 aputs(Kaccess, fout); 496 curaccess = AccessList; 497 while (curaccess) { 498 aprintf(fout, "\n\t%s", curaccess->login); 499 curaccess = curaccess->nextaccess; 500 } 501 aprintf(fout, ";\n%s", Ksymbols); 502 curassoc = Symbols; 503 while (curassoc) { 504 aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num); 505 curassoc = curassoc->nextassoc; 506 } 507 aprintf(fout, ";\n%s", Klocks); 508 curlock = Locks; 509 while (curlock) { 510 aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num); 511 curlock = curlock->nextlock; 512 } 513 if (StrictLocks) aprintf(fout, "; %s", Kstrict); 514 aprintf(fout, ";\n"); 515 if (Comment.size) { 516 aprintf(fout, "%s\t", Kcomment); 517 putstring(fout, true, Comment, false); 518 aprintf(fout, ";\n"); 519 } 520 if (Expand != KEYVAL_EXPAND) 521 aprintf(fout, "%s\t%c%s%c;\n", 522 Kexpand, SDELIM, expand_names[Expand], SDELIM 523 ); 524 awrite(Ignored.string, Ignored.size, fout); 525 aputc_('\n', fout) 526} 527 528 529 static void 530putdelta(node, fout) 531 register struct hshentry const *node; 532 register FILE * fout; 533/* Output the delta NODE to FOUT. */ 534{ 535 struct branchhead const *nextbranch; 536 537 if (!node) return; 538 539 aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches", 540 node->num, 541 Kdate, node->date, 542 Kauthor, node->author, 543 Kstate, node->state?node->state:"" 544 ); 545 nextbranch = node->branches; 546 while (nextbranch) { 547 aprintf(fout, "\n\t%s", nextbranch->hsh->num); 548 nextbranch = nextbranch->nextbranch; 549 } 550 551 aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:""); 552 awrite(node->ig.string, node->ig.size, fout); 553} 554 555 556 void 557puttree(root, fout) 558 struct hshentry const *root; 559 register FILE *fout; 560/* Output the delta tree with base ROOT in preorder to FOUT. */ 561{ 562 struct branchhead const *nextbranch; 563 564 if (!root) return; 565 566 if (root->selector) 567 putdelta(root, fout); 568 569 puttree(root->next, fout); 570 571 nextbranch = root->branches; 572 while (nextbranch) { 573 puttree(nextbranch->hsh, fout); 574 nextbranch = nextbranch->nextbranch; 575 } 576} 577 578 579 int 580putdtext(delta, srcname, fout, diffmt) 581 struct hshentry const *delta; 582 char const *srcname; 583 FILE *fout; 584 int diffmt; 585/* 586 * Output a deltatext node with delta number DELTA->num, log message DELTA->log, 587 * ignored phrases DELTA->igtext and text SRCNAME to FOUT. 588 * Double up all SDELIMs in both the log and the text. 589 * Make sure the log message ends in \n. 590 * Return false on error. 591 * If DIFFMT, also check that the text is valid diff -n output. 592 */ 593{ 594 RILE *fin; 595 if (!(fin = Iopen(srcname, "r", (struct stat*)0))) { 596 eerror(srcname); 597 return false; 598 } 599 putdftext(delta, fin, fout, diffmt); 600 Ifclose(fin); 601 return true; 602} 603 604 void 605putstring(out, delim, s, log) 606 register FILE *out; 607 struct cbuf s; 608 int delim, log; 609/* 610 * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled. 611 * If LOG is set then S is a log string; append a newline if S is nonempty. 612 */ 613{ 614 register char const *sp; 615 register size_t ss; 616 617 if (delim) 618 aputc_(SDELIM, out) 619 sp = s.string; 620 for (ss = s.size; ss; --ss) { 621 if (*sp == SDELIM) 622 aputc_(SDELIM, out) 623 aputc_(*sp++, out) 624 } 625 if (s.size && log) 626 aputc_('\n', out) 627 aputc_(SDELIM, out) 628} 629 630 void 631putdftext(delta, finfile, foutfile, diffmt) 632 struct hshentry const *delta; 633 RILE *finfile; 634 FILE *foutfile; 635 int diffmt; 636/* like putdtext(), except the source file is already open */ 637{ 638 declarecache; 639 register FILE *fout; 640 register int c; 641 register RILE *fin; 642 int ed; 643 struct diffcmd dc; 644 645 fout = foutfile; 646 aprintf(fout, DELNUMFORM, delta->num, Klog); 647 648 /* put log */ 649 putstring(fout, true, delta->log, true); 650 aputc_('\n', fout) 651 652 /* put ignored phrases */ 653 awrite(delta->igtext.string, delta->igtext.size, fout); 654 655 /* put text */ 656 aprintf(fout, "%s\n%c", Ktext, SDELIM); 657 658 fin = finfile; 659 setupcache(fin); 660 if (!diffmt) { 661 /* Copy the file */ 662 cache(fin); 663 for (;;) { 664 cachegeteof_(c, break;) 665 if (c==SDELIM) aputc_(SDELIM, fout) /*double up SDELIM*/ 666 aputc_(c, fout) 667 } 668 } else { 669 initdiffcmd(&dc); 670 while (0 <= (ed = getdiffcmd(fin, false, fout, &dc))) 671 if (ed) { 672 cache(fin); 673 while (dc.nlines--) 674 do { 675 cachegeteof_(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); }) 676 if (c == SDELIM) 677 aputc_(SDELIM, fout) 678 aputc_(c, fout) 679 } while (c != '\n'); 680 uncache(fin); 681 } 682 } 683 OK_EOF: 684 aprintf(fout, "%c\n", SDELIM); 685} 686