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