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 * 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
143 libId(genId, "$FreeBSD: stable/10/gnu/usr.bin/rcs/lib/rcsgen.c 50472 1999-08-27 23:37:10Z peter $")
144
145 int interactiveflag; /* Should we act as if stdin is a tty? */
146 struct buf curlogbuf; /* buffer for current log message */
147
148 enum stringwork { enter, copy, edit, expand, edit_expand };
149
150 static void putdelta P((struct hshentry const*,FILE*));
151 static void scandeltatext P((struct hshentry*,enum stringwork,int));
152
153
154
155
156 char const *
buildrevision(deltas,target,outfile,expandflag)157 buildrevision(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
scandeltatext(delta,func,needlog)215 scandeltatext(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
cleanlogmsg(m,s)263 cleanlogmsg(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
ttystdin()287 int 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
getcstdin()299 getcstdin()
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
yesorno(int default_answer,char const * question,...)318 yesorno(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
putdesc(textflag,textfile)346 putdesc(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
getsstdin(option,name,note,buf)421 getsstdin(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
putadmin()460 putadmin()
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
putdelta(node,fout)526 putdelta(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
puttree(root,fout)553 puttree(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
putdtext(delta,srcname,fout,diffmt)576 putdtext(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
putstring(out,delim,s,log)601 putstring(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
putdftext(delta,finfile,foutfile,diffmt)627 putdftext(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