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 
7 This file is part of RCS.
8 
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13 
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 
24 Report 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 
143 libId(genId, "$FreeBSD: stable/10/gnu/usr.bin/rcs/lib/rcsgen.c 50472 1999-08-27 23:37:10Z peter $")
144 
145 int interactiveflag;  /* Should we act as if stdin is a tty?  */
146 struct buf curlogbuf;  /* buffer for current log message */
147 
148 enum stringwork { enter, copy, edit, expand, edit_expand };
149 
150 static void putdelta P((struct hshentry const*,FILE*));
151 static void scandeltatext P((struct hshentry*,enum stringwork,int));
152 
153 
154 
155 
156 	char const *
buildrevision(deltas,target,outfile,expandflag)157 buildrevision(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
scandeltatext(delta,func,needlog)215 scandeltatext(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
cleanlogmsg(m,s)263 cleanlogmsg(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 
ttystdin()287 int 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
getcstdin()299 getcstdin()
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
yesorno(int default_answer,char const * question,...)318 yesorno(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
putdesc(textflag,textfile)346 putdesc(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
getsstdin(option,name,note,buf)421 getsstdin(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
putadmin()460 putadmin()
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
putdelta(node,fout)526 putdelta(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
puttree(root,fout)553 puttree(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
putdtext(delta,srcname,fout,diffmt)576 putdtext(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
putstring(out,delim,s,log)601 putstring(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
putdftext(delta,finfile,foutfile,diffmt)627 putdftext(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