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 7 This file is part of RCS. 8 9 RCS is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 2, or (at your option) 12 any later version. 13 14 RCS is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with RCS; see the file COPYING. 21 If not, write to the Free Software Foundation, 22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 24 Report problems and direct all questions to: 25 26 rcs-bugs@cs.purdue.edu 27 28 */ 29 30 /* 31 * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcsgen.c,v 1.7 1999/08/27 23:36:46 peter Exp $ 32 * $DragonFly: src/gnu/usr.bin/rcs/lib/rcsgen.c,v 1.3 2007/01/17 17:56:23 y0netan1 Exp $ 33 * 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 146 libId(genId, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsgen.c,v 1.3 2007/01/17 17:56:23 y0netan1 Exp $") 147 148 int interactiveflag; /* Should we act as if stdin is a tty? */ 149 struct buf curlogbuf; /* buffer for current log message */ 150 151 enum stringwork { enter, copy, edit, expand, edit_expand }; 152 153 static void putdelta P((struct hshentry const*,FILE*)); 154 static void scandeltatext P((struct hshentry*,enum stringwork,int)); 155 156 157 158 159 char const * 160 buildrevision(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 218 scandeltatext(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 266 cleanlogmsg(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 290 int 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 302 getcstdin() 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 321 yesorno(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 349 putdesc(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 424 getsstdin(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 return cleanlogmsg(p, i); 459 } 460 461 462 void 463 putadmin() 464 /* Output the admin node. */ 465 { 466 register FILE *fout; 467 struct assoc const *curassoc; 468 struct rcslock const *curlock; 469 struct access const *curaccess; 470 471 if (!(fout = frewrite)) { 472 # if bad_creat0 473 ORCSclose(); 474 fout = fopenSafer(makedirtemp(0), FOPEN_WB); 475 # else 476 int fo = fdlock; 477 fdlock = -1; 478 fout = fdopen(fo, FOPEN_WB); 479 # endif 480 481 if (!(frewrite = fout)) 482 efaterror(RCSname); 483 } 484 485 /* 486 * Output the first character with putc, not printf. 487 * Otherwise, an SVR4 stdio bug buffers output inefficiently. 488 */ 489 aputc_(*Khead, fout) 490 aprintf(fout, "%s\t%s;\n", Khead + 1, Head?Head->num:""); 491 if (Dbranch && VERSION(4)<=RCSversion) 492 aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch); 493 494 aputs(Kaccess, fout); 495 curaccess = AccessList; 496 while (curaccess) { 497 aprintf(fout, "\n\t%s", curaccess->login); 498 curaccess = curaccess->nextaccess; 499 } 500 aprintf(fout, ";\n%s", Ksymbols); 501 curassoc = Symbols; 502 while (curassoc) { 503 aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num); 504 curassoc = curassoc->nextassoc; 505 } 506 aprintf(fout, ";\n%s", Klocks); 507 curlock = Locks; 508 while (curlock) { 509 aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num); 510 curlock = curlock->nextlock; 511 } 512 if (StrictLocks) aprintf(fout, "; %s", Kstrict); 513 aprintf(fout, ";\n"); 514 if (Comment.size) { 515 aprintf(fout, "%s\t", Kcomment); 516 putstring(fout, true, Comment, false); 517 aprintf(fout, ";\n"); 518 } 519 if (Expand != KEYVAL_EXPAND) 520 aprintf(fout, "%s\t%c%s%c;\n", 521 Kexpand, SDELIM, expand_names[Expand], SDELIM 522 ); 523 awrite(Ignored.string, Ignored.size, fout); 524 aputc_('\n', fout) 525 } 526 527 528 static void 529 putdelta(node, fout) 530 register struct hshentry const *node; 531 register FILE * fout; 532 /* Output the delta NODE to FOUT. */ 533 { 534 struct branchhead const *nextbranch; 535 536 if (!node) return; 537 538 aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches", 539 node->num, 540 Kdate, node->date, 541 Kauthor, node->author, 542 Kstate, node->state?node->state:"" 543 ); 544 nextbranch = node->branches; 545 while (nextbranch) { 546 aprintf(fout, "\n\t%s", nextbranch->hsh->num); 547 nextbranch = nextbranch->nextbranch; 548 } 549 550 aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:""); 551 awrite(node->ig.string, node->ig.size, fout); 552 #ifdef RCS_EMIT_COMMITID 553 if (node->commitid) 554 aprintf(fout, "%s\t%s;\n", Kcommitid, node->commitid); 555 #endif 556 } 557 558 559 void 560 puttree(root, fout) 561 struct hshentry const *root; 562 register FILE *fout; 563 /* Output the delta tree with base ROOT in preorder to FOUT. */ 564 { 565 struct branchhead const *nextbranch; 566 567 if (!root) return; 568 569 if (root->selector) 570 putdelta(root, fout); 571 572 puttree(root->next, fout); 573 574 nextbranch = root->branches; 575 while (nextbranch) { 576 puttree(nextbranch->hsh, fout); 577 nextbranch = nextbranch->nextbranch; 578 } 579 } 580 581 582 int 583 putdtext(delta, srcname, fout, diffmt) 584 struct hshentry const *delta; 585 char const *srcname; 586 FILE *fout; 587 int diffmt; 588 /* 589 * Output a deltatext node with delta number DELTA->num, log message DELTA->log, 590 * ignored phrases DELTA->igtext and text SRCNAME to FOUT. 591 * Double up all SDELIMs in both the log and the text. 592 * Make sure the log message ends in \n. 593 * Return false on error. 594 * If DIFFMT, also check that the text is valid diff -n output. 595 */ 596 { 597 RILE *fin; 598 if (!(fin = Iopen(srcname, "r", (struct stat*)0))) { 599 eerror(srcname); 600 return false; 601 } 602 putdftext(delta, fin, fout, diffmt); 603 Ifclose(fin); 604 return true; 605 } 606 607 void 608 putstring(out, delim, s, log) 609 register FILE *out; 610 struct cbuf s; 611 int delim, log; 612 /* 613 * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled. 614 * If LOG is set then S is a log string; append a newline if S is nonempty. 615 */ 616 { 617 register char const *sp; 618 register size_t ss; 619 620 if (delim) 621 aputc_(SDELIM, out) 622 sp = s.string; 623 for (ss = s.size; ss; --ss) { 624 if (*sp == SDELIM) 625 aputc_(SDELIM, out) 626 aputc_(*sp++, out) 627 } 628 if (s.size && log) 629 aputc_('\n', out) 630 aputc_(SDELIM, out) 631 } 632 633 void 634 putdftext(delta, finfile, foutfile, diffmt) 635 struct hshentry const *delta; 636 RILE *finfile; 637 FILE *foutfile; 638 int diffmt; 639 /* like putdtext(), except the source file is already open */ 640 { 641 declarecache; 642 register FILE *fout; 643 register int c; 644 register RILE *fin; 645 int ed; 646 struct diffcmd dc; 647 648 fout = foutfile; 649 aprintf(fout, DELNUMFORM, delta->num, Klog); 650 651 /* put log */ 652 putstring(fout, true, delta->log, true); 653 aputc_('\n', fout) 654 655 /* put ignored phrases */ 656 awrite(delta->igtext.string, delta->igtext.size, fout); 657 658 /* put text */ 659 aprintf(fout, "%s\n%c", Ktext, SDELIM); 660 661 fin = finfile; 662 setupcache(fin); 663 if (!diffmt) { 664 /* Copy the file */ 665 cache(fin); 666 for (;;) { 667 cachegeteof_(c, break;) 668 if (c==SDELIM) aputc_(SDELIM, fout) /*double up SDELIM*/ 669 aputc_(c, fout) 670 } 671 } else { 672 initdiffcmd(&dc); 673 while (0 <= (ed = getdiffcmd(fin, false, fout, &dc))) 674 if (ed) { 675 cache(fin); 676 while (dc.nlines--) 677 do { 678 cachegeteof_(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); }) 679 if (c == SDELIM) 680 aputc_(SDELIM, fout) 681 aputc_(c, fout) 682 } while (c != '\n'); 683 uncache(fin); 684 } 685 } 686 OK_EOF: 687 aprintf(fout, "%c\n", SDELIM); 688 } 689