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