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