Initial import from FreeBSD RELENG_4:
[games.git] / gnu / usr.bin / rcs / lib / rcsgen.c
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: src/gnu/usr.bin/rcs/lib/rcsgen.c,v 1.7 1999/08/27 23:36:46 peter Exp $")
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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
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 }