Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.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  * $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.2 2003/06/17 04:25:47 dillon 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.2 2003/06/17 04:25:47 dillon 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 *
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
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
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
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
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
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
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
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         return cleanlogmsg(p, i);
459 }
460
461
462         void
463 putadmin()
464 /* Output the admin node.  */
465 {
466         register FILE *fout;
467         struct assoc const *curassoc;
468         struct rcslock const *curlock;
469         struct access const *curaccess;
470
471         if (!(fout = frewrite)) {
472 #               if bad_creat0
473                         ORCSclose();
474                         fout = fopenSafer(makedirtemp(0), FOPEN_WB);
475 #               else
476                         int fo = fdlock;
477                         fdlock = -1;
478                         fout = fdopen(fo, FOPEN_WB);
479 #               endif
480
481                 if (!(frewrite = fout))
482                         efaterror(RCSname);
483         }
484
485         /*
486         * Output the first character with putc, not printf.
487         * Otherwise, an SVR4 stdio bug buffers output inefficiently.
488         */
489         aputc_(*Khead, fout)
490         aprintf(fout, "%s\t%s;\n", Khead + 1, Head?Head->num:"");
491         if (Dbranch && VERSION(4)<=RCSversion)
492                 aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch);
493
494         aputs(Kaccess, fout);
495         curaccess = AccessList;
496         while (curaccess) {
497                aprintf(fout, "\n\t%s", curaccess->login);
498                curaccess = curaccess->nextaccess;
499         }
500         aprintf(fout, ";\n%s", Ksymbols);
501         curassoc = Symbols;
502         while (curassoc) {
503                aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num);
504                curassoc = curassoc->nextassoc;
505         }
506         aprintf(fout, ";\n%s", Klocks);
507         curlock = Locks;
508         while (curlock) {
509                aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num);
510                curlock = curlock->nextlock;
511         }
512         if (StrictLocks) aprintf(fout, "; %s", Kstrict);
513         aprintf(fout, ";\n");
514         if (Comment.size) {
515                 aprintf(fout, "%s\t", Kcomment);
516                 putstring(fout, true, Comment, false);
517                 aprintf(fout, ";\n");
518         }
519         if (Expand != KEYVAL_EXPAND)
520                 aprintf(fout, "%s\t%c%s%c;\n",
521                         Kexpand, SDELIM, expand_names[Expand], SDELIM
522                 );
523         awrite(Ignored.string, Ignored.size, fout);
524         aputc_('\n', fout)
525 }
526
527
528         static void
529 putdelta(node, fout)
530         register struct hshentry const *node;
531         register FILE * fout;
532 /* Output the delta NODE to FOUT.  */
533 {
534         struct branchhead const *nextbranch;
535
536         if (!node) return;
537
538         aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
539                 node->num,
540                 Kdate, node->date,
541                 Kauthor, node->author,
542                 Kstate, node->state?node->state:""
543         );
544         nextbranch = node->branches;
545         while (nextbranch) {
546                aprintf(fout, "\n\t%s", nextbranch->hsh->num);
547                nextbranch = nextbranch->nextbranch;
548         }
549
550         aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:"");
551         awrite(node->ig.string, node->ig.size, fout);
552 }
553
554
555         void
556 puttree(root, fout)
557         struct hshentry const *root;
558         register FILE *fout;
559 /* Output the delta tree with base ROOT in preorder to FOUT.  */
560 {
561         struct branchhead const *nextbranch;
562
563         if (!root) return;
564
565         if (root->selector)
566                 putdelta(root, fout);
567
568         puttree(root->next, fout);
569
570         nextbranch = root->branches;
571         while (nextbranch) {
572              puttree(nextbranch->hsh, fout);
573              nextbranch = nextbranch->nextbranch;
574         }
575 }
576
577
578         int
579 putdtext(delta, srcname, fout, diffmt)
580         struct hshentry const *delta;
581         char const *srcname;
582         FILE *fout;
583         int diffmt;
584 /*
585  * Output a deltatext node with delta number DELTA->num, log message DELTA->log,
586  * ignored phrases DELTA->igtext and text SRCNAME to FOUT.
587  * Double up all SDELIMs in both the log and the text.
588  * Make sure the log message ends in \n.
589  * Return false on error.
590  * If DIFFMT, also check that the text is valid diff -n output.
591  */
592 {
593         RILE *fin;
594         if (!(fin = Iopen(srcname, "r", (struct stat*)0))) {
595                 eerror(srcname);
596                 return false;
597         }
598         putdftext(delta, fin, fout, diffmt);
599         Ifclose(fin);
600         return true;
601 }
602
603         void
604 putstring(out, delim, s, log)
605         register FILE *out;
606         struct cbuf s;
607         int delim, log;
608 /*
609  * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled.
610  * If LOG is set then S is a log string; append a newline if S is nonempty.
611  */
612 {
613         register char const *sp;
614         register size_t ss;
615
616         if (delim)
617                 aputc_(SDELIM, out)
618         sp = s.string;
619         for (ss = s.size;  ss;  --ss) {
620                 if (*sp == SDELIM)
621                         aputc_(SDELIM, out)
622                 aputc_(*sp++, out)
623         }
624         if (s.size && log)
625                 aputc_('\n', out)
626         aputc_(SDELIM, out)
627 }
628
629         void
630 putdftext(delta, finfile, foutfile, diffmt)
631         struct hshentry const *delta;
632         RILE *finfile;
633         FILE *foutfile;
634         int diffmt;
635 /* like putdtext(), except the source file is already open */
636 {
637         declarecache;
638         register FILE *fout;
639         register int c;
640         register RILE *fin;
641         int ed;
642         struct diffcmd dc;
643
644         fout = foutfile;
645         aprintf(fout, DELNUMFORM, delta->num, Klog);
646
647         /* put log */
648         putstring(fout, true, delta->log, true);
649         aputc_('\n', fout)
650
651         /* put ignored phrases */
652         awrite(delta->igtext.string, delta->igtext.size, fout);
653
654         /* put text */
655         aprintf(fout, "%s\n%c", Ktext, SDELIM);
656
657         fin = finfile;
658         setupcache(fin);
659         if (!diffmt) {
660             /* Copy the file */
661             cache(fin);
662             for (;;) {
663                 cachegeteof_(c, break;)
664                 if (c==SDELIM) aputc_(SDELIM, fout) /*double up SDELIM*/
665                 aputc_(c, fout)
666             }
667         } else {
668             initdiffcmd(&dc);
669             while (0  <=  (ed = getdiffcmd(fin, false, fout, &dc)))
670                 if (ed) {
671                     cache(fin);
672                     while (dc.nlines--)
673                         do {
674                             cachegeteof_(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); })
675                             if (c == SDELIM)
676                                 aputc_(SDELIM, fout)
677                             aputc_(c, fout)
678                         } while (c != '\n');
679                     uncache(fin);
680                 }
681         }
682     OK_EOF:
683         aprintf(fout, "%c\n", SDELIM);
684 }