1/* $NetBSD: rcsgen.c,v 1.2 2016/01/14 04:22:39 christos Exp $ */ 2 3/* Generate RCS revisions. */ 4 5/* Copyright 1982, 1988, 1989 Walter Tichy 6 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 7 Distributed under license by the Free Software Foundation, Inc. 8 9This file is part of RCS. 10 11RCS is free software; you can redistribute it and/or modify 12it under the terms of the GNU General Public License as published by 13the Free Software Foundation; either version 2, or (at your option) 14any later version. 15 16RCS is distributed in the hope that it will be useful, 17but WITHOUT ANY WARRANTY; without even the implied warranty of 18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19GNU General Public License for more details. 20 21You should have received a copy of the GNU General Public License 22along with RCS; see the file COPYING. 23If not, write to the Free Software Foundation, 2459 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 25 26Report problems and direct all questions to: 27 28 rcs-bugs@cs.purdue.edu 29 30*/ 31 32/* 33 * Log: rcsgen.c,v 34 * Revision 5.16 1995/06/16 06:19:24 eggert 35 * Update FSF address. 36 * 37 * Revision 5.15 1995/06/01 16:23:43 eggert 38 * (putadmin): Open RCS file with FOPEN_WB. 39 * 40 * Revision 5.14 1994/03/17 14:05:48 eggert 41 * Work around SVR4 stdio performance bug. 42 * Flush stderr after prompt. Remove lint. 43 * 44 * Revision 5.13 1993/11/03 17:42:27 eggert 45 * Don't discard ignored phrases. Improve quality of diagnostics. 46 * 47 * Revision 5.12 1992/07/28 16:12:44 eggert 48 * Statement macro names now end in _. 49 * Be consistent about pathnames vs filenames. 50 * 51 * Revision 5.11 1992/01/24 18:44:19 eggert 52 * Move put routines here from rcssyn.c. 53 * Add support for bad_creat0. 54 * 55 * Revision 5.10 1991/10/07 17:32:46 eggert 56 * Fix log bugs, e.g. ci -t/dev/null when has_mmap. 57 * 58 * Revision 5.9 1991/09/10 22:15:46 eggert 59 * Fix test for redirected stdin. 60 * 61 * Revision 5.8 1991/08/19 03:13:55 eggert 62 * Add piece tables. Tune. 63 * 64 * Revision 5.7 1991/04/21 11:58:24 eggert 65 * Add MS-DOS support. 66 * 67 * Revision 5.6 1990/12/27 19:54:26 eggert 68 * Fix bug: rcs -t inserted \n, making RCS file grow. 69 * 70 * Revision 5.5 1990/12/04 05:18:45 eggert 71 * Use -I for prompts and -q for diagnostics. 72 * 73 * Revision 5.4 1990/11/01 05:03:47 eggert 74 * Add -I and new -t behavior. Permit arbitrary data in logs. 75 * 76 * Revision 5.3 1990/09/21 06:12:43 hammer 77 * made putdesc() treat stdin the same whether or not it was from a terminal 78 * by making it recognize that a single '.' was then end of the 79 * description always 80 * 81 * Revision 5.2 1990/09/04 08:02:25 eggert 82 * Fix `co -p1.1 -ko' bug. Standardize yes-or-no procedure. 83 * 84 * Revision 5.1 1990/08/29 07:14:01 eggert 85 * Clean old log messages too. 86 * 87 * Revision 5.0 1990/08/22 08:12:52 eggert 88 * Remove compile-time limits; use malloc instead. 89 * Ansify and Posixate. 90 * 91 * Revision 4.7 89/05/01 15:12:49 narten 92 * changed copyright header to reflect current distribution rules 93 * 94 * Revision 4.6 88/08/28 14:59:10 eggert 95 * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin() 96 * 97 * Revision 4.5 87/12/18 11:43:25 narten 98 * additional lint cleanups, and a bug fix from the 4.3BSD version that 99 * keeps "ci" from sticking a '\377' into the description if you run it 100 * with a zero-length file as the description. (Guy Harris) 101 * 102 * Revision 4.4 87/10/18 10:35:10 narten 103 * Updating version numbers. Changes relative to 1.1 actually relative to 104 * 4.2 105 * 106 * Revision 1.3 87/09/24 13:59:51 narten 107 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 108 * warnings) 109 * 110 * Revision 1.2 87/03/27 14:22:27 jenkins 111 * Port to suns 112 * 113 * Revision 4.2 83/12/02 23:01:39 wft 114 * merged 4.1 and 3.3.1.1 (clearerr(stdin)). 115 * 116 * Revision 4.1 83/05/10 16:03:33 wft 117 * Changed putamin() to abort if trying to reread redirected stdin. 118 * Fixed getdesc() to output a prompt on initial newline. 119 * 120 * Revision 3.3.1.1 83/10/19 04:21:51 lepreau 121 * Added clearerr(stdin) for re-reading description from stdin. 122 * 123 * Revision 3.3 82/11/28 21:36:49 wft 124 * 4.2 prerelease 125 * 126 * Revision 3.3 82/11/28 21:36:49 wft 127 * Replaced ferror() followed by fclose() with ffclose(). 128 * Putdesc() now suppresses the prompts if stdin 129 * is not a terminal. A pointer to the current log message is now 130 * inserted into the corresponding delta, rather than leaving it in a 131 * global variable. 132 * 133 * Revision 3.2 82/10/18 21:11:26 wft 134 * I added checks for write errors during editing, and improved 135 * the prompt on putdesc(). 136 * 137 * Revision 3.1 82/10/13 15:55:09 wft 138 * corrected type of variables assigned to by getc (char --> int) 139 */ 140 141 142 143 144#include "rcsbase.h" 145 146libId(genId, "Id: rcsgen.c,v 5.16 1995/06/16 06:19:24 eggert Exp ") 147 148int interactiveflag; /* Should we act as if stdin is a tty? */ 149struct buf curlogbuf; /* buffer for current log message */ 150 151enum stringwork { enter, copy, edit, expand, edit_expand }; 152 153static void putdelta P((struct hshentry const*,FILE*)); 154static void scandeltatext P((struct hshentry*,enum stringwork,int)); 155 156 157 158 159 char const * 160buildrevision(deltas, target, outfile, expandflag) 161 struct hshentries const *deltas; 162 struct hshentry *target; 163 FILE *outfile; 164 int expandflag; 165/* Function: Generates the revision given by target 166 * by retrieving all deltas given by parameter deltas and combining them. 167 * If outfile is set, the revision is output to it, 168 * otherwise written into a temporary file. 169 * Temporary files are allocated by maketemp(). 170 * if expandflag is set, keyword expansion is performed. 171 * Return 0 if outfile is set, the name of the temporary file otherwise. 172 * 173 * Algorithm: Copy initial revision unchanged. Then edit all revisions but 174 * the last one into it, alternating input and output files (resultname and 175 * editname). The last revision is then edited in, performing simultaneous 176 * keyword substitution (this saves one extra pass). 177 * All this simplifies if only one revision needs to be generated, 178 * or no keyword expansion is necessary, or if output goes to stdout. 179 */ 180{ 181 if (deltas->first == target) { 182 /* only latest revision to generate */ 183 openfcopy(outfile); 184 scandeltatext(target, expandflag?expand:copy, true); 185 if (outfile) 186 return 0; 187 else { 188 Ozclose(&fcopy); 189 return resultname; 190 } 191 } else { 192 /* several revisions to generate */ 193 /* Get initial revision without keyword expansion. */ 194 scandeltatext(deltas->first, enter, false); 195 while ((deltas=deltas->rest)->rest) { 196 /* do all deltas except last one */ 197 scandeltatext(deltas->first, edit, false); 198 } 199 if (expandflag || outfile) { 200 /* first, get to beginning of file*/ 201 finishedit((struct hshentry*)0, outfile, false); 202 } 203 scandeltatext(target, expandflag?edit_expand:edit, true); 204 finishedit( 205 expandflag ? target : (struct hshentry*)0, 206 outfile, true 207 ); 208 if (outfile) 209 return 0; 210 Ozclose(&fcopy); 211 return resultname; 212 } 213} 214 215 216 217 static void 218scandeltatext(delta, func, needlog) 219 struct hshentry *delta; 220 enum stringwork func; 221 int needlog; 222/* Function: Scans delta text nodes up to and including the one given 223 * by delta. For the one given by delta, the log message is saved into 224 * delta->log if needlog is set; func specifies how to handle the text. 225 * Similarly, if needlog, delta->igtext is set to the ignored phrases. 226 * Assumes the initial lexeme must be read in first. 227 * Does not advance nexttok after it is finished. 228 */ 229{ 230 struct hshentry const *nextdelta; 231 struct cbuf cb; 232 233 for (;;) { 234 if (eoflex()) 235 fatserror("can't find delta for revision %s", delta->num); 236 nextlex(); 237 if (!(nextdelta=getnum())) { 238 fatserror("delta number corrupted"); 239 } 240 getkeystring(Klog); 241 if (needlog && delta==nextdelta) { 242 cb = savestring(&curlogbuf); 243 delta->log = cleanlogmsg(curlogbuf.string, cb.size); 244 nextlex(); 245 delta->igtext = getphrases(Ktext); 246 } else {readstring(); 247 ignorephrases(Ktext); 248 } 249 getkeystring(Ktext); 250 251 if (delta==nextdelta) 252 break; 253 readstring(); /* skip over it */ 254 255 } 256 switch (func) { 257 case enter: enterstring(); break; 258 case copy: copystring(); break; 259 case expand: xpandstring(delta); break; 260 case edit: editstring((struct hshentry *)0); break; 261 case edit_expand: editstring(delta); break; 262 } 263} 264 265 struct cbuf 266cleanlogmsg(m, s) 267 char *m; 268 size_t s; 269{ 270 register char *t = m; 271 register char const *f = t; 272 struct cbuf r; 273 while (s) { 274 --s; 275 if ((*t++ = *f++) == '\n') 276 while (m < --t) 277 if (t[-1]!=' ' && t[-1]!='\t') { 278 *t++ = '\n'; 279 break; 280 } 281 } 282 while (m < t && (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n')) 283 --t; 284 r.string = m; 285 r.size = t - m; 286 return r; 287} 288 289 290int ttystdin() 291{ 292 static int initialized; 293 if (!initialized) { 294 if (!interactiveflag) 295 interactiveflag = isatty(STDIN_FILENO); 296 initialized = true; 297 } 298 return interactiveflag; 299} 300 301 int 302getcstdin() 303{ 304 register FILE *in; 305 register int c; 306 307 in = stdin; 308 if (feof(in) && ttystdin()) 309 clearerr(in); 310 c = getc(in); 311 if (c == EOF) { 312 testIerror(in); 313 if (feof(in) && ttystdin()) 314 afputc('\n',stderr); 315 } 316 return c; 317} 318 319#if has_prototypes 320 int 321yesorno(int default_answer, char const *question, ...) 322#else 323 /*VARARGS2*/ int 324 yesorno(default_answer, question, va_alist) 325 int default_answer; char const *question; va_dcl 326#endif 327{ 328 va_list args; 329 register int c, r; 330 if (!quietflag && ttystdin()) { 331 oflush(); 332 vararg_start(args, question); 333 fvfprintf(stderr, question, args); 334 va_end(args); 335 eflush(); 336 r = c = getcstdin(); 337 while (c!='\n' && !feof(stdin)) 338 c = getcstdin(); 339 if (r=='y' || r=='Y') 340 return true; 341 if (r=='n' || r=='N') 342 return false; 343 } 344 return default_answer; 345} 346 347 348 void 349putdesc(textflag, textfile) 350 int textflag; 351 char *textfile; 352/* Function: puts the descriptive text into file frewrite. 353 * if finptr && !textflag, the text is copied from the old description. 354 * Otherwise, if textfile, the text is read from that 355 * file, or from stdin, if !textfile. 356 * A textfile with a leading '-' is treated as a string, not a pathname. 357 * If finptr, the old descriptive text is discarded. 358 * Always clears foutptr. 359 */ 360{ 361 static struct buf desc; 362 static struct cbuf desclean; 363 364 register FILE *txt; 365 register int c; 366 register FILE * frew; 367 register char *p; 368 register size_t s; 369 char const *plim; 370 371 frew = frewrite; 372 if (finptr && !textflag) { 373 /* copy old description */ 374 aprintf(frew, "\n\n%s%c", Kdesc, nextc); 375 foutptr = frewrite; 376 getdesc(false); 377 foutptr = 0; 378 } else { 379 foutptr = 0; 380 /* get new description */ 381 if (finptr) { 382 /*skip old description*/ 383 getdesc(false); 384 } 385 aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM); 386 if (!textfile) 387 desclean = getsstdin( 388 "t-", "description", 389 "NOTE: This is NOT the log message!\n", &desc 390 ); 391 else if (!desclean.string) { 392 if (*textfile == '-') { 393 p = textfile + 1; 394 s = strlen(p); 395 } else { 396 if (!(txt = fopenSafer(textfile, "r"))) 397 efaterror(textfile); 398 bufalloc(&desc, 1); 399 p = desc.string; 400 plim = p + desc.size; 401 for (;;) { 402 if ((c=getc(txt)) == EOF) { 403 testIerror(txt); 404 if (feof(txt)) 405 break; 406 } 407 if (plim <= p) 408 p = bufenlarge(&desc, &plim); 409 *p++ = c; 410 } 411 if (fclose(txt) != 0) 412 Ierror(); 413 s = p - desc.string; 414 p = desc.string; 415 } 416 desclean = cleanlogmsg(p, s); 417 } 418 putstring(frew, false, desclean, true); 419 aputc_('\n', frew) 420 } 421} 422 423 struct cbuf 424getsstdin(option, name, note, buf) 425 char const *option, *name, *note; 426 struct buf *buf; 427{ 428 register int c; 429 register char *p; 430 register size_t i; 431 register int tty = ttystdin(); 432 433 if (tty) { 434 aprintf(stderr, 435 "enter %s, terminated with single '.' or end of file:\n%s>> ", 436 name, note 437 ); 438 eflush(); 439 } else if (feof(stdin)) 440 rcsfaterror("can't reread redirected stdin for %s; use -%s<%s>", 441 name, option, name 442 ); 443 444 for ( 445 i = 0, p = 0; 446 c = getcstdin(), !feof(stdin); 447 bufrealloc(buf, i+1), p = buf->string, p[i++] = c 448 ) 449 if (c == '\n') { 450 if (i && p[i-1]=='.' && (i==1 || p[i-2]=='\n')) { 451 /* Remove trailing '.'. */ 452 --i; 453 break; 454 } else if (tty) { 455 aputs(">> ", stderr); 456 eflush(); 457 } 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