1 /* Generate RCS revisions.
2 
3    Copyright (C) 2010-2020 Thien-Thi Nguyen
4    Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5    Copyright (C) 1982, 1988, 1989 Walter Tichy
6 
7    This file is part of GNU RCS.
8 
9    GNU RCS is free software: you can redistribute it and/or modify it
10    under the terms of the GNU General Public License as published by
11    the Free Software Foundation, either version 3 of the License, or
12    (at your option) any later version.
13 
14    GNU RCS is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty
16    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17    See the GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #include "base.h"
24 #include <string.h>
25 #include <stdarg.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #include "b-complain.h"
29 #include "b-divvy.h"
30 #include "b-esds.h"
31 #include "b-fb.h"
32 #include "b-feph.h"
33 #include "b-fro.h"
34 #include "b-kwxout.h"
35 
36 enum stringwork
37 { enter, copy, edit, expand, edit_expand };
38 
39 static void
scandeltatext(struct editstuff * es,struct wlink ** ls,struct delta * delta,enum stringwork func,bool needlog)40 scandeltatext (struct editstuff *es, struct wlink **ls,
41                struct delta *delta, enum stringwork func, bool needlog)
42 /* Scan delta text nodes up to and including the one given by ‘delta’.
43    For the one given by ‘delta’, the log message is saved into
44    ‘delta->log’ if ‘needlog’ is set; ‘func’ specifies how to handle the
45    text.  Does not advance input after finished.  */
46 {
47   struct delta const *nextdelta;
48   struct fro *from = FLOW (from);
49   FILE *to = FLOW (to);
50   struct atat *log, *text;
51   struct range range;
52 
53   for (;; *ls = (*ls)->next)
54     {
55       nextdelta = (*ls)->entry;
56       log = nextdelta->log;
57       text = nextdelta->text;
58       range.beg = nextdelta->neck;
59       range.end = text->beg;
60       if (needlog && delta == nextdelta)
61         {
62           /* TODO: Make ‘needlog’ a ‘struct cbuf *’ and stash there.  */
63           delta->pretty_log = string_from_atat (SINGLE, log);
64           delta->pretty_log = cleanlogmsg (delta->pretty_log.string,
65                                            delta->pretty_log.size);
66         }
67       /* Skip over it.  */
68       if (to)
69         fro_spew_partial (to, from, &range);
70       if (delta == nextdelta)
71         break;
72       /* Skip over it.  */
73       if (to)
74         atat_put (to, text);
75     }
76   fro_move (from, range.end);
77   switch (func)
78     {
79     case enter:
80       enterstring (es, text);
81       break;
82     case copy:
83       copystring (es, text);
84       break;
85     case expand:
86       /* Read a string terminated by ‘SDELIM’ from ‘FLOW (from)’ and
87          write it to ‘FLOW (res)’.  Double ‘SDELIM’ is replaced with
88          single ‘SDELIM’.  Keyword expansion is performed with data
89          from ‘delta’.  If ‘FLOW (to)’ is non-NULL, the string is
90          also copied unchanged to ‘FLOW (to)’.  */
91       {
92         int c;
93         struct expctx ctx = EXPCTX (FLOW (res), to, from, true, true);
94 
95         GETCHAR (c, from);
96         if (to)
97           afputc (c, to);
98         while (1 < expandline (&ctx))
99           continue;
100         FINISH_EXPCTX (&ctx);
101       }
102       break;
103     case edit:
104       editstring (es, text, NULL);
105       break;
106     case edit_expand:
107       editstring (es, text, delta);
108       break;
109     }
110 }
111 
112 char const *
buildrevision(struct wlink const * deltas,struct delta * target,FILE * outfile,bool expandflag)113 buildrevision (struct wlink const *deltas, struct delta *target,
114                FILE *outfile, bool expandflag)
115 /* Generate the revision given by ‘target’ by retrieving all deltas given
116    by parameter ‘deltas’ and combining them.  If ‘outfile’ is set, the
117    revision is output to it, otherwise write into a temporary file.
118    Temporary files are allocated by ‘maketemp’.  If ‘expandflag’ is set,
119    keyword expansion is performed.  Return NULL if ‘outfile’ is set, the
120    name of the temporary file otherwise.
121 
122    Algorithm: Copy initial revision unchanged.  Then edit all revisions
123    but the last one into it, alternating input and output files
124    (‘FLOW (result)’ and ‘editname’).  The last revision is then edited in,
125    performing simultaneous keyword substitution (this saves one extra
126    pass).  All this simplifies if only one revision needs to be generated,
127    or no keyword expansion is necessary, or if output goes to stdout.  */
128 {
129   struct editstuff *es = make_editstuff ();
130   struct wlink *ls = GROK (deltas);
131 
132   if (deltas->entry == target)
133     {
134       /* Only latest revision to generate.  */
135       openfcopy (outfile);
136       scandeltatext (es, &ls, target,
137                      expandflag ? expand : copy,
138                      true);
139     }
140   else
141     {
142       /* Several revisions to generate.
143          Get initial revision without keyword expansion.  */
144       scandeltatext (es, &ls, deltas->entry, enter, false);
145       while (ls = ls->next,
146              (deltas = deltas->next)->next)
147         {
148           /* Do all deltas except last one.  */
149           scandeltatext (es, &ls, deltas->entry, edit, false);
150         }
151       if (expandflag || outfile)
152         {
153           /* First, get to beginning of file.  */
154           finishedit (es, NULL, outfile, false);
155         }
156       scandeltatext (es, &ls, target,
157                      expandflag ? edit_expand : edit,
158                      true);
159       finishedit (es,
160                   expandflag ? target : NULL,
161                   outfile, true);
162     }
163   unmake_editstuff (es);
164   if (outfile)
165     return NULL;
166   Ozclose (&FLOW (res));
167   return FLOW (result);
168 }
169 
170 struct cbuf
cleanlogmsg(char const * m,size_t s)171 cleanlogmsg (char const *m, size_t s)
172 {
173   struct cbuf r;
174 
175 #define WHITESPACEP(c)  (' ' == c || '\t' == c || '\n' == c)
176   while (s && WHITESPACEP (*m))
177     s--, m++;
178   while (s && WHITESPACEP (m[s - 1]))
179     s--;
180 #undef WHITESPACEP
181 
182   r.string = m;
183   r.size = s;
184   return r;
185 }
186 
187 bool
ttystdin(void)188 ttystdin (void)
189 {
190   if (!BE (interactive_valid))
191     {
192       if (!BE (interactive))
193         BE (interactive) = isatty (STDIN_FILENO);
194       BE (interactive_valid) = true;
195     }
196   return BE (interactive);
197 }
198 
199 int
getcstdin(void)200 getcstdin (void)
201 {
202   register FILE *in;
203   register int c;
204 
205   in = stdin;
206   if (feof (in) && ttystdin ())
207     clearerr (in);
208   c = getc (in);
209   if (c == EOF)
210     {
211       testIerror (in);
212       if (feof (in) && ttystdin ())
213         complain ("\n");
214     }
215   return c;
216 }
217 
218 bool
yesorno(bool default_answer,char const * question,...)219 yesorno (bool default_answer, char const *question, ...)
220 {
221   va_list args;
222   register int c, r;
223 
224   if (!BE (quiet) && ttystdin ())
225     {
226       char *ans = default_answer
227         ? "yn"
228         : "ny";
229 
230       oflush ();
231       va_start (args, question);
232       vcomplain (question, args);
233       va_end (args);
234       complain ("? [%s](%c): ", ans, ans[0]);
235       r = c = getcstdin ();
236       while (c != '\n' && !feof (stdin))
237         c = getcstdin ();
238       if (r == 'y' || r == 'Y')
239         return true;
240       if (r == 'n' || r == 'N')
241         return false;
242     }
243   return default_answer;
244 }
245 
246 void
write_desc_maybe(FILE * to)247 write_desc_maybe (FILE *to)
248 {
249   struct atat *desc = GROK (desc);
250 
251   if (to)
252     atat_put (to, desc);
253 }
254 
255 void
putdesc(struct cbuf * cb,bool textflag,char * textfile)256 putdesc (struct cbuf *cb, bool textflag, char *textfile)
257 /* Put the descriptive text into file ‘FLOW (rewr)’.
258    Also, save the description text into ‘cb’.
259    If ‘FLOW (from) && !textflag’, the text is copied from the old description.
260    Otherwise, if ‘textfile’, the text is read from that file, or from
261    stdin, if ‘!textfile’.  A ‘textfile’ with a leading '-' is treated as a
262    string, not a filename.  If ‘FLOW (from)’, the old descriptive text is
263    discarded.  Always clear ‘FLOW (to)’.  */
264 {
265   register FILE *txt;
266   register int c;
267   register FILE *frew;
268   register char *p;
269   size_t s;
270   struct fro *from = FLOW (from);
271 
272   frew = FLOW (rewr);
273   if (from && !textflag)
274     {
275       /* Copy old description.  */
276       aprintf (frew, "\n\n%s\n", TINYKS (desc));
277       write_desc_maybe (frew);
278     }
279   else
280     {
281       FLOW (to) = NULL;
282       /* Get new description.  */
283       aprintf (frew, "\n\n%s\n", TINYKS (desc));
284       if (!textfile)
285         *cb = getsstdin ("t-", "description",
286                          "NOTE: This is NOT the log message!\n");
287       else if (!cb->string)
288         {
289           if (*textfile == '-')
290             {
291               p = textfile + 1;
292               s = strlen (p);
293             }
294           else
295             {
296               if (!(txt = fopen_safer (textfile, "r")))
297                 fatal_sys (textfile);
298               for (;;)
299                 {
300                   if ((c = getc (txt)) == EOF)
301                     {
302                       testIerror (txt);
303                       if (feof (txt))
304                         break;
305                     }
306                   accumulate_byte (PLEXUS, c);
307                 }
308               if (PROB (fclose (txt)))
309                 Ierror ();
310               p = finish_string (PLEXUS, &s);
311             }
312           *cb = cleanlogmsg (p, s);
313         }
314       putstring (frew, *cb, true);
315       newline (frew);
316     }
317 }
318 
319 struct cbuf
getsstdin(char const * option,char const * name,char const * note)320 getsstdin (char const *option, char const *name, char const *note)
321 {
322   register int c;
323   register char *p;
324   register bool tty = ttystdin ();
325   size_t len, column = 0;
326   bool dot_in_first_column = false, discard = false;
327 
328 #define prompt  complain
329   if (tty)
330     prompt ("enter %s, terminated with single '.' or end of file:\n%s>> ",
331             name, note);
332   else if (feof (stdin))
333     RFATAL ("can't reread redirected stdin for %s; use -%s<%s>",
334             name, option, name);
335 
336   while (c = getcstdin (), !feof (stdin))
337     {
338       if (!column)
339         dot_in_first_column = ('.' == c);
340       if (c == '\n')
341         {
342           if (1 == column && dot_in_first_column)
343             {
344               discard = true;
345               break;
346             }
347           else if (tty)
348             prompt (">> ");
349           column = 0;
350         }
351       else
352         column++;
353       accumulate_byte (PLEXUS, c);
354     }
355   p = finish_string (PLEXUS, &len);
356 #undef prompt
357   return cleanlogmsg (p, len - (discard ? 1 : 0));
358 }
359 
360 void
format_assocs(FILE * out,char const * fmt)361 format_assocs (FILE *out, char const *fmt)
362 {
363   for (struct link *ls = GROK (symbols); ls; ls = ls->next)
364     {
365       struct symdef const *d = ls->entry;
366 
367       aprintf (out, fmt, d->meaningful, d->underlying);
368     }
369 }
370 
371 void
format_locks(FILE * out,char const * fmt)372 format_locks (FILE *out, char const *fmt)
373 {
374   for (struct link *ls = GROK (locks); ls; ls = ls->next)
375     {
376       struct rcslock const *rl = ls->entry;
377 
378       aprintf (out, fmt, rl->login, rl->delta->num);
379     }
380 }
381 
382 static char const *semi_lf = ";\n";
383 #define SEMI_LF()  aprintf (fout, "%s", semi_lf)
384 
385 void
putadmin(void)386 putadmin (void)
387 /* Output the admin node.  */
388 {
389   register FILE *fout;
390   struct repo *r = REPO (r);
391   struct delta *tip = REPO (tip);
392   char const *defbr = r ? GROK (branch) : NULL;
393   int kws = BE (kws);
394 
395   if (!(fout = FLOW (rewr)))
396     {
397       if (BAD_CREAT0)
398         {
399           ORCSclose ();
400           fout = fopen_safer (makedirtemp (false), FOPEN_WB);
401         }
402       else
403         {
404           int fo = REPO (fd_lock);
405 
406           REPO (fd_lock) = -1;
407           fout = fdopen (fo, FOPEN_WB);
408         }
409 
410       if (!(FLOW (rewr) = fout))
411         fatal_sys (REPO (filename));
412     }
413 
414   aprintf (fout, "%s\t%s%s", TINYKS (head),
415            tip ? tip->num : "",
416            semi_lf);
417   if (defbr && VERSION (4) <= BE (version))
418     aprintf (fout, "%s\t%s%s", TINYKS (branch), defbr, semi_lf);
419   aputs (TINYKS (access), fout);
420   for (struct link *ls = r ? GROK (access) : NULL;
421        ls;
422        ls = ls->next)
423     aprintf (fout, "\n\t%s", (char const *) ls->entry);
424   SEMI_LF ();
425   aprintf (fout, "%s", TINYKS (symbols));
426   format_assocs (fout, "\n\t%s:%s"); SEMI_LF ();
427   aprintf (fout, "%s", TINYKS (locks));
428   if (r)
429     format_locks (fout, "\n\t%s:%s");
430   if (BE (strictly_locking))
431     aprintf (fout, "; %s", TINYKS (strict));
432   SEMI_LF ();
433   if (GROK (integrity))
434     {
435       aprintf (fout, "%s\n", TINYKS (integrity));
436       atat_put (fout, GROK (integrity)); SEMI_LF ();
437     }
438   if (REPO (log_lead).size)
439     {
440       aprintf (fout, "%s\t", TINYKS (comment));
441       putstring (fout, REPO (log_lead), false); SEMI_LF ();
442     }
443   if (kws != kwsub_kv)
444     aprintf (fout, "%s\t%c%s%c%s",
445              TINYKS (expand), SDELIM, kwsub_string (kws),
446              SDELIM, semi_lf);
447   aprintf (fout, "\n");
448 }
449 
450 static void
putdelta(register struct delta const * node,register FILE * fout)451 putdelta (register struct delta const *node, register FILE *fout)
452 /* Output the delta ‘node’ to ‘fout’.  */
453 {
454   if (!node)
455     return;
456 
457   aprintf (fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s%s%s",
458            node->num, TINYKS (date), node->date, TINYKS (author), node->author,
459            TINYKS (state),
460            node->state ? node->state : "",
461            semi_lf, TINYKS (branches));
462   for (struct wlink *ls = node->branches; ls; ls = ls->next)
463     {
464       struct delta *delta = ls->entry;
465 
466       aprintf (fout, "\n\t%s", delta->num);
467     }
468   SEMI_LF ();
469 
470   aprintf (fout, "%s\t%s", TINYKS (next),
471            node->ilk ? node->ilk->num : "");
472   SEMI_LF ();
473   if (node->commitid)
474     aprintf (fout, "%s\t%s%s", TINYKS (commitid), node->commitid, semi_lf);
475 }
476 
477 void
puttree(struct delta const * root,register FILE * fout)478 puttree (struct delta const *root, register FILE *fout)
479 /* Output the delta tree with base ‘root’ in preorder to ‘fout’.  */
480 {
481   while (root)
482     {
483       if (root->selector)
484         putdelta (root, fout);
485 
486       struct wlink *ls = root->branches;
487       if (! ls)
488         root = root->ilk;
489       else
490         {
491           /* FIXME: This recurses deeply in the worst case.  */
492           puttree (root->ilk, fout);
493           for (; ls->next; ls = ls->next)
494             puttree (ls->entry, fout);
495           root = ls->entry;
496         }
497     }
498 }
499 
500 bool
putdtext(struct delta const * delta,char const * srcname,FILE * fout,bool diffmt)501 putdtext (struct delta const *delta, char const *srcname,
502           FILE *fout, bool diffmt)
503 /* Output a deltatext node with delta number ‘delta->num’, log message
504    ‘delta->pretty_log’, and text ‘srcname’ to ‘fout’.  Double up all ‘SDELIM’s
505    in both the log and the text.  Make sure the log message ends in '\n'.
506    Return false on error.  If ‘diffmt’, also check that the text is valid
507    "diff -n" output.  */
508 {
509   struct fro *fin;
510 
511   if (!(fin = fro_open (srcname, "r", NULL)))
512     {
513       syserror_errno (srcname);
514       return false;
515     }
516   putdftext (delta, fin, fout, diffmt);
517   fro_close (fin);
518   return true;
519 }
520 
521 static void
put_SDELIM(FILE * out)522 put_SDELIM (FILE *out)
523 {
524   aputc (SDELIM, out);
525 }
526 
527 void
putstring(register FILE * out,struct cbuf s,bool log)528 putstring (register FILE *out, struct cbuf s, bool log)
529 /* Output to ‘out’ one ‘SDELIM’, then string ‘s’ with ‘SDELIM’s doubled.
530    If ‘log’ is set then ‘s’ is a log string; append a newline if ‘s’ is
531    nonempty.  Finally, output a terminating ‘SDELIM’.  */
532 {
533   register char const *sp;
534   char const *delim;
535   register size_t ss, span;
536 
537   put_SDELIM (out);
538   for (sp = s.string, ss = s.size;
539        ss;
540        sp += span, ss -= span)
541     {
542       delim = memchr (sp, SDELIM, ss);
543       span = delim
544         ? (size_t) (delim - sp) + 1U
545         : ss;
546 
547       awrite (sp, span, out);
548       if (delim)
549         put_SDELIM (out);
550     }
551   if (s.size && log)
552     newline (out);
553   put_SDELIM (out);
554 }
555 
556 void
putdftext(struct delta const * delta,struct fro * finfile,FILE * foutfile,bool diffmt)557 putdftext (struct delta const *delta, struct fro *finfile,
558            FILE *foutfile, bool diffmt)
559 /* Like ‘putdtext’, except the source file is already open.  */
560 {
561   register FILE *fout;
562   int c;
563   register struct fro *fin;
564   int ed;
565   struct diffcmd dc;
566 
567   fout = foutfile;
568   aprintf (fout, "\n\n%s\n%s\n", delta->num, TINYKS (log));
569 
570   /* Put log.  */
571   putstring (fout, delta->pretty_log, true);
572   newline (fout);
573   /* Put text.  */
574   aprintf (fout, "%s\n%c", TINYKS (text), SDELIM);
575 
576   fin = finfile;
577   if (!diffmt)
578     {
579       /* Copy the file.  */
580       for (;;)
581         {
582           GETCHAR_OR (c, fin, goto done);
583           if (c == SDELIM)
584             /* Double up ‘SDELIM’.  */
585             put_SDELIM (fout);
586           aputc (c, fout);
587         }
588     done:
589       ;
590     }
591   else
592     {
593       initdiffcmd (&dc);
594       while (0 <= (ed = getdiffcmd (fin, false, fout, &dc)))
595         if (ed)
596           {
597             while (dc.nlines--)
598               do
599                 {
600                   GETCHAR_OR (c, fin,
601                               {
602                                 if (!dc.nlines)
603                                   goto OK_EOF;
604                                 unexpected_EOF ();
605                               });
606                   if (c == SDELIM)
607                     put_SDELIM (fout);
608                   aputc (c, fout);
609                 }
610               while (c != '\n');
611           }
612     }
613  OK_EOF:
614   aprintf (fout, "%c\n", SDELIM);
615 }
616 
617 /* rcsgen.c ends here */
618