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 * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcsgen.c,v 1.7 1999/08/27 23:36:46 peter Exp $
32 * $DragonFly: src/gnu/usr.bin/rcs/lib/rcsgen.c,v 1.3 2007/01/17 17:56:23 y0netan1 Exp $
33 *
34 * Revision 5.16 1995/06/16 06:19:24 eggert
35 * Update FSF address.
36 *
37 * Revision 5.15 1995/06/01 16:23:43 eggert
38 * (putadmin): Open RCS file with FOPEN_WB.
39 *
40 * Revision 5.14 1994/03/17 14:05:48 eggert
41 * Work around SVR4 stdio performance bug.
42 * Flush stderr after prompt. Remove lint.
43 *
44 * Revision 5.13 1993/11/03 17:42:27 eggert
45 * Don't discard ignored phrases. Improve quality of diagnostics.
46 *
47 * Revision 5.12 1992/07/28 16:12:44 eggert
48 * Statement macro names now end in _.
49 * Be consistent about pathnames vs filenames.
50 *
51 * Revision 5.11 1992/01/24 18:44:19 eggert
52 * Move put routines here from rcssyn.c.
53 * Add support for bad_creat0.
54 *
55 * Revision 5.10 1991/10/07 17:32:46 eggert
56 * Fix log bugs, e.g. ci -t/dev/null when has_mmap.
57 *
58 * Revision 5.9 1991/09/10 22:15:46 eggert
59 * Fix test for redirected stdin.
60 *
61 * Revision 5.8 1991/08/19 03:13:55 eggert
62 * Add piece tables. Tune.
63 *
64 * Revision 5.7 1991/04/21 11:58:24 eggert
65 * Add MS-DOS support.
66 *
67 * Revision 5.6 1990/12/27 19:54:26 eggert
68 * Fix bug: rcs -t inserted \n, making RCS file grow.
69 *
70 * Revision 5.5 1990/12/04 05:18:45 eggert
71 * Use -I for prompts and -q for diagnostics.
72 *
73 * Revision 5.4 1990/11/01 05:03:47 eggert
74 * Add -I and new -t behavior. Permit arbitrary data in logs.
75 *
76 * Revision 5.3 1990/09/21 06:12:43 hammer
77 * made putdesc() treat stdin the same whether or not it was from a terminal
78 * by making it recognize that a single '.' was then end of the
79 * description always
80 *
81 * Revision 5.2 1990/09/04 08:02:25 eggert
82 * Fix `co -p1.1 -ko' bug. Standardize yes-or-no procedure.
83 *
84 * Revision 5.1 1990/08/29 07:14:01 eggert
85 * Clean old log messages too.
86 *
87 * Revision 5.0 1990/08/22 08:12:52 eggert
88 * Remove compile-time limits; use malloc instead.
89 * Ansify and Posixate.
90 *
91 * Revision 4.7 89/05/01 15:12:49 narten
92 * changed copyright header to reflect current distribution rules
93 *
94 * Revision 4.6 88/08/28 14:59:10 eggert
95 * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin()
96 *
97 * Revision 4.5 87/12/18 11:43:25 narten
98 * additional lint cleanups, and a bug fix from the 4.3BSD version that
99 * keeps "ci" from sticking a '\377' into the description if you run it
100 * with a zero-length file as the description. (Guy Harris)
101 *
102 * Revision 4.4 87/10/18 10:35:10 narten
103 * Updating version numbers. Changes relative to 1.1 actually relative to
104 * 4.2
105 *
106 * Revision 1.3 87/09/24 13:59:51 narten
107 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
108 * warnings)
109 *
110 * Revision 1.2 87/03/27 14:22:27 jenkins
111 * Port to suns
112 *
113 * Revision 4.2 83/12/02 23:01:39 wft
114 * merged 4.1 and 3.3.1.1 (clearerr(stdin)).
115 *
116 * Revision 4.1 83/05/10 16:03:33 wft
117 * Changed putamin() to abort if trying to reread redirected stdin.
118 * Fixed getdesc() to output a prompt on initial newline.
119 *
120 * Revision 3.3.1.1 83/10/19 04:21:51 lepreau
121 * Added clearerr(stdin) for re-reading description from stdin.
122 *
123 * Revision 3.3 82/11/28 21:36:49 wft
124 * 4.2 prerelease
125 *
126 * Revision 3.3 82/11/28 21:36:49 wft
127 * Replaced ferror() followed by fclose() with ffclose().
128 * Putdesc() now suppresses the prompts if stdin
129 * is not a terminal. A pointer to the current log message is now
130 * inserted into the corresponding delta, rather than leaving it in a
131 * global variable.
132 *
133 * Revision 3.2 82/10/18 21:11:26 wft
134 * I added checks for write errors during editing, and improved
135 * the prompt on putdesc().
136 *
137 * Revision 3.1 82/10/13 15:55:09 wft
138 * corrected type of variables assigned to by getc (char --> int)
139 */
140
141
142
143
144 #include "rcsbase.h"
145
146 libId(genId, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsgen.c,v 1.3 2007/01/17 17:56:23 y0netan1 Exp $")
147
148 int interactiveflag; /* Should we act as if stdin is a tty? */
149 struct buf curlogbuf; /* buffer for current log message */
150
151 enum stringwork { enter, copy, edit, expand, edit_expand };
152
153 static void putdelta P((struct hshentry const*,FILE*));
154 static void scandeltatext P((struct hshentry*,enum stringwork,int));
155
156
157
158
159 char const *
buildrevision(deltas,target,outfile,expandflag)160 buildrevision(deltas, target, outfile, expandflag)
161 struct hshentries const *deltas;
162 struct hshentry *target;
163 FILE *outfile;
164 int expandflag;
165 /* Function: Generates the revision given by target
166 * by retrieving all deltas given by parameter deltas and combining them.
167 * If outfile is set, the revision is output to it,
168 * otherwise written into a temporary file.
169 * Temporary files are allocated by maketemp().
170 * if expandflag is set, keyword expansion is performed.
171 * Return 0 if outfile is set, the name of the temporary file otherwise.
172 *
173 * Algorithm: Copy initial revision unchanged. Then edit all revisions but
174 * the last one into it, alternating input and output files (resultname and
175 * editname). The last revision is then edited in, performing simultaneous
176 * keyword substitution (this saves one extra pass).
177 * All this simplifies if only one revision needs to be generated,
178 * or no keyword expansion is necessary, or if output goes to stdout.
179 */
180 {
181 if (deltas->first == target) {
182 /* only latest revision to generate */
183 openfcopy(outfile);
184 scandeltatext(target, expandflag?expand:copy, true);
185 if (outfile)
186 return 0;
187 else {
188 Ozclose(&fcopy);
189 return resultname;
190 }
191 } else {
192 /* several revisions to generate */
193 /* Get initial revision without keyword expansion. */
194 scandeltatext(deltas->first, enter, false);
195 while ((deltas=deltas->rest)->rest) {
196 /* do all deltas except last one */
197 scandeltatext(deltas->first, edit, false);
198 }
199 if (expandflag || outfile) {
200 /* first, get to beginning of file*/
201 finishedit((struct hshentry*)0, outfile, false);
202 }
203 scandeltatext(target, expandflag?edit_expand:edit, true);
204 finishedit(
205 expandflag ? target : (struct hshentry*)0,
206 outfile, true
207 );
208 if (outfile)
209 return 0;
210 Ozclose(&fcopy);
211 return resultname;
212 }
213 }
214
215
216
217 static void
scandeltatext(delta,func,needlog)218 scandeltatext(delta, func, needlog)
219 struct hshentry *delta;
220 enum stringwork func;
221 int needlog;
222 /* Function: Scans delta text nodes up to and including the one given
223 * by delta. For the one given by delta, the log message is saved into
224 * delta->log if needlog is set; func specifies how to handle the text.
225 * Similarly, if needlog, delta->igtext is set to the ignored phrases.
226 * Assumes the initial lexeme must be read in first.
227 * Does not advance nexttok after it is finished.
228 */
229 {
230 struct hshentry const *nextdelta;
231 struct cbuf cb;
232
233 for (;;) {
234 if (eoflex())
235 fatserror("can't find delta for revision %s", delta->num);
236 nextlex();
237 if (!(nextdelta=getnum())) {
238 fatserror("delta number corrupted");
239 }
240 getkeystring(Klog);
241 if (needlog && delta==nextdelta) {
242 cb = savestring(&curlogbuf);
243 delta->log = cleanlogmsg(curlogbuf.string, cb.size);
244 nextlex();
245 delta->igtext = getphrases(Ktext);
246 } else {readstring();
247 ignorephrases(Ktext);
248 }
249 getkeystring(Ktext);
250
251 if (delta==nextdelta)
252 break;
253 readstring(); /* skip over it */
254
255 }
256 switch (func) {
257 case enter: enterstring(); break;
258 case copy: copystring(); break;
259 case expand: xpandstring(delta); break;
260 case edit: editstring((struct hshentry *)0); break;
261 case edit_expand: editstring(delta); break;
262 }
263 }
264
265 struct cbuf
cleanlogmsg(m,s)266 cleanlogmsg(m, s)
267 char *m;
268 size_t s;
269 {
270 register char *t = m;
271 register char const *f = t;
272 struct cbuf r;
273 while (s) {
274 --s;
275 if ((*t++ = *f++) == '\n')
276 while (m < --t)
277 if (t[-1]!=' ' && t[-1]!='\t') {
278 *t++ = '\n';
279 break;
280 }
281 }
282 while (m < t && (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n'))
283 --t;
284 r.string = m;
285 r.size = t - m;
286 return r;
287 }
288
289
ttystdin()290 int ttystdin()
291 {
292 static int initialized;
293 if (!initialized) {
294 if (!interactiveflag)
295 interactiveflag = isatty(STDIN_FILENO);
296 initialized = true;
297 }
298 return interactiveflag;
299 }
300
301 int
getcstdin()302 getcstdin()
303 {
304 register FILE *in;
305 register int c;
306
307 in = stdin;
308 if (feof(in) && ttystdin())
309 clearerr(in);
310 c = getc(in);
311 if (c == EOF) {
312 testIerror(in);
313 if (feof(in) && ttystdin())
314 afputc('\n',stderr);
315 }
316 return c;
317 }
318
319 #if has_prototypes
320 int
yesorno(int default_answer,char const * question,...)321 yesorno(int default_answer, char const *question, ...)
322 #else
323 /*VARARGS2*/ int
324 yesorno(default_answer, question, va_alist)
325 int default_answer; char const *question; va_dcl
326 #endif
327 {
328 va_list args;
329 register int c, r;
330 if (!quietflag && ttystdin()) {
331 oflush();
332 vararg_start(args, question);
333 fvfprintf(stderr, question, args);
334 va_end(args);
335 eflush();
336 r = c = getcstdin();
337 while (c!='\n' && !feof(stdin))
338 c = getcstdin();
339 if (r=='y' || r=='Y')
340 return true;
341 if (r=='n' || r=='N')
342 return false;
343 }
344 return default_answer;
345 }
346
347
348 void
putdesc(textflag,textfile)349 putdesc(textflag, textfile)
350 int textflag;
351 char *textfile;
352 /* Function: puts the descriptive text into file frewrite.
353 * if finptr && !textflag, the text is copied from the old description.
354 * Otherwise, if textfile, the text is read from that
355 * file, or from stdin, if !textfile.
356 * A textfile with a leading '-' is treated as a string, not a pathname.
357 * If finptr, the old descriptive text is discarded.
358 * Always clears foutptr.
359 */
360 {
361 static struct buf desc;
362 static struct cbuf desclean;
363
364 register FILE *txt;
365 register int c;
366 register FILE * frew;
367 register char *p;
368 register size_t s;
369 char const *plim;
370
371 frew = frewrite;
372 if (finptr && !textflag) {
373 /* copy old description */
374 aprintf(frew, "\n\n%s%c", Kdesc, nextc);
375 foutptr = frewrite;
376 getdesc(false);
377 foutptr = 0;
378 } else {
379 foutptr = 0;
380 /* get new description */
381 if (finptr) {
382 /*skip old description*/
383 getdesc(false);
384 }
385 aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM);
386 if (!textfile)
387 desclean = getsstdin(
388 "t-", "description",
389 "NOTE: This is NOT the log message!\n", &desc
390 );
391 else if (!desclean.string) {
392 if (*textfile == '-') {
393 p = textfile + 1;
394 s = strlen(p);
395 } else {
396 if (!(txt = fopenSafer(textfile, "r")))
397 efaterror(textfile);
398 bufalloc(&desc, 1);
399 p = desc.string;
400 plim = p + desc.size;
401 for (;;) {
402 if ((c=getc(txt)) == EOF) {
403 testIerror(txt);
404 if (feof(txt))
405 break;
406 }
407 if (plim <= p)
408 p = bufenlarge(&desc, &plim);
409 *p++ = c;
410 }
411 if (fclose(txt) != 0)
412 Ierror();
413 s = p - desc.string;
414 p = desc.string;
415 }
416 desclean = cleanlogmsg(p, s);
417 }
418 putstring(frew, false, desclean, true);
419 aputc_('\n', frew)
420 }
421 }
422
423 struct cbuf
getsstdin(option,name,note,buf)424 getsstdin(option, name, note, buf)
425 char const *option, *name, *note;
426 struct buf *buf;
427 {
428 register int c;
429 register char *p;
430 register size_t i;
431 register int tty = ttystdin();
432
433 if (tty) {
434 aprintf(stderr,
435 "enter %s, terminated with single '.' or end of file:\n%s>> ",
436 name, note
437 );
438 eflush();
439 } else if (feof(stdin))
440 rcsfaterror("can't reread redirected stdin for %s; use -%s<%s>",
441 name, option, name
442 );
443
444 for (
445 i = 0, p = 0;
446 c = getcstdin(), !feof(stdin);
447 bufrealloc(buf, i+1), p = buf->string, p[i++] = c
448 )
449 if (c == '\n') {
450 if (i && p[i-1]=='.' && (i==1 || p[i-2]=='\n')) {
451 /* Remove trailing '.'. */
452 --i;
453 break;
454 } else if (tty) {
455 aputs(">> ", stderr);
456 eflush();
457 }
458 }
459 return cleanlogmsg(p, i);
460 }
461
462
463 void
putadmin()464 putadmin()
465 /* Output the admin node. */
466 {
467 register FILE *fout;
468 struct assoc const *curassoc;
469 struct rcslock const *curlock;
470 struct access const *curaccess;
471
472 if (!(fout = frewrite)) {
473 # if bad_creat0
474 ORCSclose();
475 fout = fopenSafer(makedirtemp(0), FOPEN_WB);
476 # else
477 int fo = fdlock;
478 fdlock = -1;
479 fout = fdopen(fo, FOPEN_WB);
480 # endif
481
482 if (!(frewrite = fout))
483 efaterror(RCSname);
484 }
485
486 /*
487 * Output the first character with putc, not printf.
488 * Otherwise, an SVR4 stdio bug buffers output inefficiently.
489 */
490 aputc_(*Khead, fout)
491 aprintf(fout, "%s\t%s;\n", Khead + 1, Head?Head->num:"");
492 if (Dbranch && VERSION(4)<=RCSversion)
493 aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch);
494
495 aputs(Kaccess, fout);
496 curaccess = AccessList;
497 while (curaccess) {
498 aprintf(fout, "\n\t%s", curaccess->login);
499 curaccess = curaccess->nextaccess;
500 }
501 aprintf(fout, ";\n%s", Ksymbols);
502 curassoc = Symbols;
503 while (curassoc) {
504 aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num);
505 curassoc = curassoc->nextassoc;
506 }
507 aprintf(fout, ";\n%s", Klocks);
508 curlock = Locks;
509 while (curlock) {
510 aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num);
511 curlock = curlock->nextlock;
512 }
513 if (StrictLocks) aprintf(fout, "; %s", Kstrict);
514 aprintf(fout, ";\n");
515 if (Comment.size) {
516 aprintf(fout, "%s\t", Kcomment);
517 putstring(fout, true, Comment, false);
518 aprintf(fout, ";\n");
519 }
520 if (Expand != KEYVAL_EXPAND)
521 aprintf(fout, "%s\t%c%s%c;\n",
522 Kexpand, SDELIM, expand_names[Expand], SDELIM
523 );
524 awrite(Ignored.string, Ignored.size, fout);
525 aputc_('\n', fout)
526 }
527
528
529 static void
putdelta(node,fout)530 putdelta(node, fout)
531 register struct hshentry const *node;
532 register FILE * fout;
533 /* Output the delta NODE to FOUT. */
534 {
535 struct branchhead const *nextbranch;
536
537 if (!node) return;
538
539 aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
540 node->num,
541 Kdate, node->date,
542 Kauthor, node->author,
543 Kstate, node->state?node->state:""
544 );
545 nextbranch = node->branches;
546 while (nextbranch) {
547 aprintf(fout, "\n\t%s", nextbranch->hsh->num);
548 nextbranch = nextbranch->nextbranch;
549 }
550
551 aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:"");
552 awrite(node->ig.string, node->ig.size, fout);
553 #ifdef RCS_EMIT_COMMITID
554 if (node->commitid)
555 aprintf(fout, "%s\t%s;\n", Kcommitid, node->commitid);
556 #endif
557 }
558
559
560 void
puttree(root,fout)561 puttree(root, fout)
562 struct hshentry const *root;
563 register FILE *fout;
564 /* Output the delta tree with base ROOT in preorder to FOUT. */
565 {
566 struct branchhead const *nextbranch;
567
568 if (!root) return;
569
570 if (root->selector)
571 putdelta(root, fout);
572
573 puttree(root->next, fout);
574
575 nextbranch = root->branches;
576 while (nextbranch) {
577 puttree(nextbranch->hsh, fout);
578 nextbranch = nextbranch->nextbranch;
579 }
580 }
581
582
583 int
putdtext(delta,srcname,fout,diffmt)584 putdtext(delta, srcname, fout, diffmt)
585 struct hshentry const *delta;
586 char const *srcname;
587 FILE *fout;
588 int diffmt;
589 /*
590 * Output a deltatext node with delta number DELTA->num, log message DELTA->log,
591 * ignored phrases DELTA->igtext and text SRCNAME to FOUT.
592 * Double up all SDELIMs in both the log and the text.
593 * Make sure the log message ends in \n.
594 * Return false on error.
595 * If DIFFMT, also check that the text is valid diff -n output.
596 */
597 {
598 RILE *fin;
599 if (!(fin = Iopen(srcname, "r", (struct stat*)0))) {
600 eerror(srcname);
601 return false;
602 }
603 putdftext(delta, fin, fout, diffmt);
604 Ifclose(fin);
605 return true;
606 }
607
608 void
putstring(out,delim,s,log)609 putstring(out, delim, s, log)
610 register FILE *out;
611 struct cbuf s;
612 int delim, log;
613 /*
614 * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled.
615 * If LOG is set then S is a log string; append a newline if S is nonempty.
616 */
617 {
618 register char const *sp;
619 register size_t ss;
620
621 if (delim)
622 aputc_(SDELIM, out)
623 sp = s.string;
624 for (ss = s.size; ss; --ss) {
625 if (*sp == SDELIM)
626 aputc_(SDELIM, out)
627 aputc_(*sp++, out)
628 }
629 if (s.size && log)
630 aputc_('\n', out)
631 aputc_(SDELIM, out)
632 }
633
634 void
putdftext(delta,finfile,foutfile,diffmt)635 putdftext(delta, finfile, foutfile, diffmt)
636 struct hshentry const *delta;
637 RILE *finfile;
638 FILE *foutfile;
639 int diffmt;
640 /* like putdtext(), except the source file is already open */
641 {
642 declarecache;
643 register FILE *fout;
644 register int c;
645 register RILE *fin;
646 int ed;
647 struct diffcmd dc;
648
649 fout = foutfile;
650 aprintf(fout, DELNUMFORM, delta->num, Klog);
651
652 /* put log */
653 putstring(fout, true, delta->log, true);
654 aputc_('\n', fout)
655
656 /* put ignored phrases */
657 awrite(delta->igtext.string, delta->igtext.size, fout);
658
659 /* put text */
660 aprintf(fout, "%s\n%c", Ktext, SDELIM);
661
662 fin = finfile;
663 setupcache(fin);
664 if (!diffmt) {
665 /* Copy the file */
666 cache(fin);
667 for (;;) {
668 cachegeteof_(c, break;)
669 if (c==SDELIM) aputc_(SDELIM, fout) /*double up SDELIM*/
670 aputc_(c, fout)
671 }
672 } else {
673 initdiffcmd(&dc);
674 while (0 <= (ed = getdiffcmd(fin, false, fout, &dc)))
675 if (ed) {
676 cache(fin);
677 while (dc.nlines--)
678 do {
679 cachegeteof_(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); })
680 if (c == SDELIM)
681 aputc_(SDELIM, fout)
682 aputc_(c, fout)
683 } while (c != '\n');
684 uncache(fin);
685 }
686 }
687 OK_EOF:
688 aprintf(fout, "%c\n", SDELIM);
689 }
690