rcsgen.c revision 8858
1/*
2 *                     RCS revision generation
3 */
4
5/* Copyright (C) 1982, 1988, 1989 Walter Tichy
6   Copyright 1990, 1991 by 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.  If not, write to
23the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24
25Report problems and direct all questions to:
26
27    rcs-bugs@cs.purdue.edu
28
29*/
30
31
32
33/* $Log: rcsgen.c,v $
34 * Revision 1.1.1.1  1993/06/18  04:22:12  jkh
35 * Updated GNU utilities
36 *
37 * Revision 5.10  1991/10/07  17:32:46  eggert
38 * Fix log bugs, e.g. ci -t/dev/null when has_mmap.
39 *
40 * Revision 5.9  1991/09/10  22:15:46  eggert
41 * Fix test for redirected stdin.
42 *
43 * Revision 5.8  1991/08/19  03:13:55  eggert
44 * Add piece tables.  Tune.
45 *
46 * Revision 5.7  1991/04/21  11:58:24  eggert
47 * Add MS-DOS support.
48 *
49 * Revision 5.6  1990/12/27  19:54:26  eggert
50 * Fix bug: rcs -t inserted \n, making RCS file grow.
51 *
52 * Revision 5.5  1990/12/04  05:18:45  eggert
53 * Use -I for prompts and -q for diagnostics.
54 *
55 * Revision 5.4  1990/11/01  05:03:47  eggert
56 * Add -I and new -t behavior.  Permit arbitrary data in logs.
57 *
58 * Revision 5.3  1990/09/21  06:12:43  hammer
59 * made putdesc() treat stdin the same whether or not it was from a terminal
60 * by making it recognize that a single '.' was then end of the
61 * description always
62 *
63 * Revision 5.2  1990/09/04  08:02:25  eggert
64 * Fix `co -p1.1 -ko' bug.  Standardize yes-or-no procedure.
65 *
66 * Revision 5.1  1990/08/29  07:14:01  eggert
67 * Clean old log messages too.
68 *
69 * Revision 5.0  1990/08/22  08:12:52  eggert
70 * Remove compile-time limits; use malloc instead.
71 * Ansify and Posixate.
72 *
73 * Revision 4.7  89/05/01  15:12:49  narten
74 * changed copyright header to reflect current distribution rules
75 *
76 * Revision 4.6  88/08/28  14:59:10  eggert
77 * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin()
78 *
79 * Revision 4.5  87/12/18  11:43:25  narten
80 * additional lint cleanups, and a bug fix from the 4.3BSD version that
81 * keeps "ci" from sticking a '\377' into the description if you run it
82 * with a zero-length file as the description. (Guy Harris)
83 *
84 * Revision 4.4  87/10/18  10:35:10  narten
85 * Updating version numbers. Changes relative to 1.1 actually relative to
86 * 4.2
87 *
88 * Revision 1.3  87/09/24  13:59:51  narten
89 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
90 * warnings)
91 *
92 * Revision 1.2  87/03/27  14:22:27  jenkins
93 * Port to suns
94 *
95 * Revision 4.2  83/12/02  23:01:39  wft
96 * merged 4.1 and 3.3.1.1 (clearerr(stdin)).
97 *
98 * Revision 4.1  83/05/10  16:03:33  wft
99 * Changed putamin() to abort if trying to reread redirected stdin.
100 * Fixed getdesc() to output a prompt on initial newline.
101 *
102 * Revision 3.3.1.1  83/10/19  04:21:51  lepreau
103 * Added clearerr(stdin) for re-reading description from stdin.
104 *
105 * Revision 3.3  82/11/28  21:36:49  wft
106 * 4.2 prerelease
107 *
108 * Revision 3.3  82/11/28  21:36:49  wft
109 * Replaced ferror() followed by fclose() with ffclose().
110 * Putdesc() now suppresses the prompts if stdin
111 * is not a terminal. A pointer to the current log message is now
112 * inserted into the corresponding delta, rather than leaving it in a
113 * global variable.
114 *
115 * Revision 3.2  82/10/18  21:11:26  wft
116 * I added checks for write errors during editing, and improved
117 * the prompt on putdesc().
118 *
119 * Revision 3.1  82/10/13  15:55:09  wft
120 * corrected type of variables assigned to by getc (char --> int)
121 */
122
123
124
125
126#include "rcsbase.h"
127
128libId(genId, "$Id: rcsgen.c,v 1.1.1.1 1993/06/18 04:22:12 jkh Exp $")
129
130int interactiveflag;  /* Should we act as if stdin is a tty?  */
131struct buf curlogbuf;  /* buffer for current log message */
132
133enum stringwork { enter, copy, edit, expand, edit_expand };
134static void scandeltatext P((struct hshentry*,enum stringwork,int));
135
136
137
138
139	char const *
140buildrevision(deltas, target, outfile, expandflag)
141	struct hshentries const *deltas;
142	struct hshentry *target;
143	FILE *outfile;
144	int expandflag;
145/* Function: Generates the revision given by target
146 * by retrieving all deltas given by parameter deltas and combining them.
147 * If outfile is set, the revision is output to it,
148 * otherwise written into a temporary file.
149 * Temporary files are allocated by maketemp().
150 * if expandflag is set, keyword expansion is performed.
151 * Return nil if outfile is set, the name of the temporary file otherwise.
152 *
153 * Algorithm: Copy initial revision unchanged.  Then edit all revisions but
154 * the last one into it, alternating input and output files (resultfile and
155 * editfile). The last revision is then edited in, performing simultaneous
156 * keyword substitution (this saves one extra pass).
157 * All this simplifies if only one revision needs to be generated,
158 * or no keyword expansion is necessary, or if output goes to stdout.
159 */
160{
161	if (deltas->first == target) {
162                /* only latest revision to generate */
163		openfcopy(outfile);
164		scandeltatext(target, expandflag?expand:copy, true);
165		if (outfile)
166			return 0;
167		else {
168			Ozclose(&fcopy);
169                        return(resultfile);
170		}
171        } else {
172                /* several revisions to generate */
173		/* Get initial revision without keyword expansion.  */
174		scandeltatext(deltas->first, enter, false);
175		while ((deltas=deltas->rest)->rest) {
176                        /* do all deltas except last one */
177			scandeltatext(deltas->first, edit, false);
178                }
179		if (expandflag || outfile) {
180                        /* first, get to beginning of file*/
181			finishedit((struct hshentry *)nil, outfile, false);
182                }
183		scandeltatext(deltas->first, expandflag?edit_expand:edit, true);
184		finishedit(
185			expandflag ? deltas->first : (struct hshentry*)nil,
186			outfile, true
187		);
188		if (outfile)
189			return 0;
190		Ozclose(&fcopy);
191		return resultfile;
192        }
193}
194
195
196
197	static void
198scandeltatext(delta, func, needlog)
199	struct hshentry * delta;
200	enum stringwork func;
201	int needlog;
202/* Function: Scans delta text nodes up to and including the one given
203 * by delta. For the one given by delta, the log message is saved into
204 * delta->log if needlog is set; func specifies how to handle the text.
205 * Assumes the initial lexeme must be read in first.
206 * Does not advance nexttok after it is finished.
207 */
208{
209	struct hshentry const *nextdelta;
210	struct cbuf cb;
211
212        for (;;) {
213		if (eoflex())
214		    fatserror("can't find delta for revision %s", delta->num);
215                nextlex();
216                if (!(nextdelta=getnum())) {
217		    fatserror("delta number corrupted");
218                }
219		getkeystring(Klog);
220		if (needlog && delta==nextdelta) {
221			cb = savestring(&curlogbuf);
222			delta->log = cleanlogmsg(curlogbuf.string, cb.size);
223                } else {readstring();
224                }
225                nextlex();
226		while (nexttok==ID && strcmp(NextString,Ktext)!=0)
227			ignorephrase();
228		getkeystring(Ktext);
229
230		if (delta==nextdelta)
231			break;
232		readstring(); /* skip over it */
233
234	}
235	switch (func) {
236		case enter: enterstring(); break;
237		case copy: copystring(); break;
238		case expand: xpandstring(delta); break;
239		case edit: editstring((struct hshentry *)nil); break;
240		case edit_expand: editstring(delta); break;
241	}
242}
243
244	struct cbuf
245cleanlogmsg(m, s)
246	char *m;
247	size_t s;
248{
249	register char *t = m;
250	register char const *f = t;
251	struct cbuf r;
252	while (s) {
253	    --s;
254	    if ((*t++ = *f++) == '\n')
255		while (m < --t)
256		    if (t[-1]!=' ' && t[-1]!='\t') {
257			*t++ = '\n';
258			break;
259		    }
260	}
261	while (m < t  &&  (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n'))
262	    --t;
263	r.string = m;
264	r.size = t - m;
265	return r;
266}
267
268
269int ttystdin()
270{
271	static int initialized;
272	if (!initialized) {
273		if (!interactiveflag)
274			interactiveflag = isatty(STDIN_FILENO);
275		initialized = true;
276	}
277	return interactiveflag;
278}
279
280	int
281getcstdin()
282{
283	register FILE *in;
284	register int c;
285
286	in = stdin;
287	if (feof(in) && ttystdin())
288		clearerr(in);
289	c = getc(in);
290	if (c < 0) {
291		testIerror(in);
292		if (feof(in) && ttystdin())
293			afputc('\n',stderr);
294	}
295	return c;
296}
297
298#if has_prototypes
299	int
300yesorno(int default_answer, char const *question, ...)
301#else
302		/*VARARGS2*/ int
303	yesorno(default_answer, question, va_alist)
304		int default_answer; char const *question; va_dcl
305#endif
306{
307	va_list args;
308	register int c, r;
309	if (!quietflag && ttystdin()) {
310		oflush();
311		vararg_start(args, question);
312		fvfprintf(stderr, question, args);
313		va_end(args);
314		eflush();
315		r = c = getcstdin();
316		while (c!='\n' && !feof(stdin))
317			c = getcstdin();
318		if (r=='y' || r=='Y')
319			return true;
320		if (r=='n' || r=='N')
321			return false;
322	}
323	return default_answer;
324}
325
326
327	void
328putdesc(textflag, textfile)
329	int textflag;
330	char *textfile;
331/* Function: puts the descriptive text into file frewrite.
332 * if finptr && !textflag, the text is copied from the old description.
333 * Otherwise, if the textfile!=nil, the text is read from that
334 * file, or from stdin, if textfile==nil.
335 * A textfile with a leading '-' is treated as a string, not a file name.
336 * If finptr, the old descriptive text is discarded.
337 * Always clears foutptr.
338 */
339{
340	static struct buf desc;
341	static struct cbuf desclean;
342
343	register FILE *txt;
344	register int c;
345	register FILE * frew;
346	register char *p;
347	register size_t s;
348	char const *plim;
349
350	frew = frewrite;
351	if (finptr && !textflag) {
352                /* copy old description */
353		aprintf(frew, "\n\n%s%c", Kdesc, nextc);
354		foutptr = frewrite;
355		getdesc(false);
356		foutptr = 0;
357        } else {
358		foutptr = 0;
359                /* get new description */
360		if (finptr) {
361                        /*skip old description*/
362			getdesc(false);
363                }
364		aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM);
365		if (!textfile)
366			desclean = getsstdin(
367				"t-", "description",
368				"NOTE: This is NOT the log message!\n", &desc
369			);
370		else if (!desclean.string) {
371			if (*textfile == '-') {
372				p = textfile + 1;
373				s = strlen(p);
374			} else {
375				if (!(txt = fopen(textfile, "r")))
376					efaterror(textfile);
377				bufalloc(&desc, 1);
378				p = desc.string;
379				plim = p + desc.size;
380				for (;;) {
381					if ((c=getc(txt)) < 0) {
382						testIerror(txt);
383						if (feof(txt))
384							break;
385					}
386					if (plim <= p)
387					    p = bufenlarge(&desc, &plim);
388					*p++ = c;
389				}
390				if (fclose(txt) != 0)
391					Ierror();
392				s = p - desc.string;
393				p = desc.string;
394			}
395			desclean = cleanlogmsg(p, s);
396		}
397		putstring(frew, false, desclean, true);
398		aputc('\n', frew);
399        }
400}
401
402	struct cbuf
403getsstdin(option, name, note, buf)
404	char const *option, *name, *note;
405	struct buf *buf;
406{
407	register int c;
408	register char *p;
409	register size_t i;
410	register int tty = ttystdin();
411
412	if (tty)
413	    aprintf(stderr,
414		"enter %s, terminated with single '.' or end of file:\n%s>> ",
415		name, note
416	    );
417	else if (feof(stdin))
418	    faterror("can't reread redirected stdin for %s; use -%s<%s>",
419		name, option, name
420	    );
421
422	for (
423	   i = 0,  p = 0;
424	   c = getcstdin(),  !feof(stdin);
425	   bufrealloc(buf, i+1),  p = buf->string,  p[i++] = c
426	)
427		if (c == '\n')
428			if (i  &&  p[i-1]=='.'  &&  (i==1 || p[i-2]=='\n')) {
429				/* Remove trailing '.'.  */
430				--i;
431				break;
432			} else if (tty)
433				aputs(">> ", stderr);
434	return cleanlogmsg(p, i);
435}
436