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