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