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 * 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
143libId(genId, "$FreeBSD$")
144
145int interactiveflag;  /* Should we act as if stdin is a tty?  */
146struct buf curlogbuf;  /* buffer for current log message */
147
148enum stringwork { enter, copy, edit, expand, edit_expand };
149
150static void putdelta P((struct hshentry const*,FILE*));
151static void scandeltatext P((struct hshentry*,enum stringwork,int));
152
153
154
155
156	char const *
157buildrevision(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
215scandeltatext(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
263cleanlogmsg(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
287int 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
299getcstdin()
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
318yesorno(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
346putdesc(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
421getsstdin(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
460putadmin()
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
526putdelta(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
553puttree(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
576putdtext(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
601putstring(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
627putdftext(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