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 } 459 return cleanlogmsg(p, i); 460 } 461 462 463 void 464 putadmin() 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 530 putdelta(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 #ifdef RCS_EMIT_COMMITID 554 if (node->commitid) 555 aprintf(fout, "%s\t%s;\n", Kcommitid, node->commitid); 556 #endif 557 } 558 559 560 void 561 puttree(root, fout) 562 struct hshentry const *root; 563 register FILE *fout; 564 /* Output the delta tree with base ROOT in preorder to FOUT. */ 565 { 566 struct branchhead const *nextbranch; 567 568 if (!root) return; 569 570 if (root->selector) 571 putdelta(root, fout); 572 573 puttree(root->next, fout); 574 575 nextbranch = root->branches; 576 while (nextbranch) { 577 puttree(nextbranch->hsh, fout); 578 nextbranch = nextbranch->nextbranch; 579 } 580 } 581 582 583 int 584 putdtext(delta, srcname, fout, diffmt) 585 struct hshentry const *delta; 586 char const *srcname; 587 FILE *fout; 588 int diffmt; 589 /* 590 * Output a deltatext node with delta number DELTA->num, log message DELTA->log, 591 * ignored phrases DELTA->igtext and text SRCNAME to FOUT. 592 * Double up all SDELIMs in both the log and the text. 593 * Make sure the log message ends in \n. 594 * Return false on error. 595 * If DIFFMT, also check that the text is valid diff -n output. 596 */ 597 { 598 RILE *fin; 599 if (!(fin = Iopen(srcname, "r", (struct stat*)0))) { 600 eerror(srcname); 601 return false; 602 } 603 putdftext(delta, fin, fout, diffmt); 604 Ifclose(fin); 605 return true; 606 } 607 608 void 609 putstring(out, delim, s, log) 610 register FILE *out; 611 struct cbuf s; 612 int delim, log; 613 /* 614 * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled. 615 * If LOG is set then S is a log string; append a newline if S is nonempty. 616 */ 617 { 618 register char const *sp; 619 register size_t ss; 620 621 if (delim) 622 aputc_(SDELIM, out) 623 sp = s.string; 624 for (ss = s.size; ss; --ss) { 625 if (*sp == SDELIM) 626 aputc_(SDELIM, out) 627 aputc_(*sp++, out) 628 } 629 if (s.size && log) 630 aputc_('\n', out) 631 aputc_(SDELIM, out) 632 } 633 634 void 635 putdftext(delta, finfile, foutfile, diffmt) 636 struct hshentry const *delta; 637 RILE *finfile; 638 FILE *foutfile; 639 int diffmt; 640 /* like putdtext(), except the source file is already open */ 641 { 642 declarecache; 643 register FILE *fout; 644 register int c; 645 register RILE *fin; 646 int ed; 647 struct diffcmd dc; 648 649 fout = foutfile; 650 aprintf(fout, DELNUMFORM, delta->num, Klog); 651 652 /* put log */ 653 putstring(fout, true, delta->log, true); 654 aputc_('\n', fout) 655 656 /* put ignored phrases */ 657 awrite(delta->igtext.string, delta->igtext.size, fout); 658 659 /* put text */ 660 aprintf(fout, "%s\n%c", Ktext, SDELIM); 661 662 fin = finfile; 663 setupcache(fin); 664 if (!diffmt) { 665 /* Copy the file */ 666 cache(fin); 667 for (;;) { 668 cachegeteof_(c, break;) 669 if (c==SDELIM) aputc_(SDELIM, fout) /*double up SDELIM*/ 670 aputc_(c, fout) 671 } 672 } else { 673 initdiffcmd(&dc); 674 while (0 <= (ed = getdiffcmd(fin, false, fout, &dc))) 675 if (ed) { 676 cache(fin); 677 while (dc.nlines--) 678 do { 679 cachegeteof_(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); }) 680 if (c == SDELIM) 681 aputc_(SDELIM, fout) 682 aputc_(c, fout) 683 } while (c != '\n'); 684 uncache(fin); 685 } 686 } 687 OK_EOF: 688 aprintf(fout, "%c\n", SDELIM); 689 } 690