1/*	$NetBSD: rcsgen.c,v 1.2 2016/01/14 04:22:39 christos Exp $	*/
2
3/* Generate RCS revisions.  */
4
5/* Copyright 1982, 1988, 1989 Walter Tichy
6   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7   Distributed under license by the Free Software Foundation, Inc.
8
9This file is part of RCS.
10
11RCS is free software; you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation; either version 2, or (at your option)
14any later version.
15
16RCS is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with RCS; see the file COPYING.
23If not, write to the Free Software Foundation,
2459 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26Report problems and direct all questions to:
27
28    rcs-bugs@cs.purdue.edu
29
30*/
31
32/*
33 * Log: rcsgen.c,v
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
146libId(genId, "Id: rcsgen.c,v 5.16 1995/06/16 06:19:24 eggert Exp ")
147
148int interactiveflag;  /* Should we act as if stdin is a tty?  */
149struct buf curlogbuf;  /* buffer for current log message */
150
151enum stringwork { enter, copy, edit, expand, edit_expand };
152
153static void putdelta P((struct hshentry const*,FILE*));
154static void scandeltatext P((struct hshentry*,enum stringwork,int));
155
156
157
158
159	char const *
160buildrevision(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
218scandeltatext(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
266cleanlogmsg(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
290int 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
302getcstdin()
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
321yesorno(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
349putdesc(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
424getsstdin(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
464putadmin()
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
530putdelta(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}
554
555
556	void
557puttree(root, fout)
558	struct hshentry const *root;
559	register FILE *fout;
560/* Output the delta tree with base ROOT in preorder to FOUT.  */
561{
562	struct branchhead const *nextbranch;
563
564	if (!root) return;
565
566	if (root->selector)
567		putdelta(root, fout);
568
569	puttree(root->next, fout);
570
571	nextbranch = root->branches;
572	while (nextbranch) {
573	     puttree(nextbranch->hsh, fout);
574	     nextbranch = nextbranch->nextbranch;
575	}
576}
577
578
579	int
580putdtext(delta, srcname, fout, diffmt)
581	struct hshentry const *delta;
582	char const *srcname;
583	FILE *fout;
584	int diffmt;
585/*
586 * Output a deltatext node with delta number DELTA->num, log message DELTA->log,
587 * ignored phrases DELTA->igtext and text SRCNAME to FOUT.
588 * Double up all SDELIMs in both the log and the text.
589 * Make sure the log message ends in \n.
590 * Return false on error.
591 * If DIFFMT, also check that the text is valid diff -n output.
592 */
593{
594	RILE *fin;
595	if (!(fin = Iopen(srcname, "r", (struct stat*)0))) {
596		eerror(srcname);
597		return false;
598	}
599	putdftext(delta, fin, fout, diffmt);
600	Ifclose(fin);
601	return true;
602}
603
604	void
605putstring(out, delim, s, log)
606	register FILE *out;
607	struct cbuf s;
608	int delim, log;
609/*
610 * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled.
611 * If LOG is set then S is a log string; append a newline if S is nonempty.
612 */
613{
614	register char const *sp;
615	register size_t ss;
616
617	if (delim)
618		aputc_(SDELIM, out)
619	sp = s.string;
620	for (ss = s.size;  ss;  --ss) {
621		if (*sp == SDELIM)
622			aputc_(SDELIM, out)
623		aputc_(*sp++, out)
624	}
625	if (s.size && log)
626		aputc_('\n', out)
627	aputc_(SDELIM, out)
628}
629
630	void
631putdftext(delta, finfile, foutfile, diffmt)
632	struct hshentry const *delta;
633	RILE *finfile;
634	FILE *foutfile;
635	int diffmt;
636/* like putdtext(), except the source file is already open */
637{
638	declarecache;
639	register FILE *fout;
640	register int c;
641	register RILE *fin;
642	int ed;
643	struct diffcmd dc;
644
645	fout = foutfile;
646	aprintf(fout, DELNUMFORM, delta->num, Klog);
647
648	/* put log */
649	putstring(fout, true, delta->log, true);
650	aputc_('\n', fout)
651
652	/* put ignored phrases */
653	awrite(delta->igtext.string, delta->igtext.size, fout);
654
655	/* put text */
656	aprintf(fout, "%s\n%c", Ktext, SDELIM);
657
658	fin = finfile;
659	setupcache(fin);
660	if (!diffmt) {
661	    /* Copy the file */
662	    cache(fin);
663	    for (;;) {
664		cachegeteof_(c, break;)
665		if (c==SDELIM) aputc_(SDELIM, fout) /*double up SDELIM*/
666		aputc_(c, fout)
667	    }
668	} else {
669	    initdiffcmd(&dc);
670	    while (0  <=  (ed = getdiffcmd(fin, false, fout, &dc)))
671		if (ed) {
672		    cache(fin);
673		    while (dc.nlines--)
674			do {
675			    cachegeteof_(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); })
676			    if (c == SDELIM)
677				aputc_(SDELIM, fout)
678			    aputc_(c, fout)
679			} while (c != '\n');
680		    uncache(fin);
681		}
682	}
683    OK_EOF:
684	aprintf(fout, "%c\n", SDELIM);
685}
686