1 /* b-grok.c --- comma-v parsing
2
3 Copyright (C) 2010-2020 Thien-Thi Nguyen
4
5 This file is part of GNU RCS.
6
7 GNU RCS is free software: you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU RCS is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty
14 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "base.h"
22 #include <stdarg.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <unistd.h>
26 #include "hash-pjw.h"
27 #include "b-complain.h"
28 #include "b-divvy.h"
29 #include "b-esds.h"
30 #include "b-fro.h"
31 #include "b-grok.h"
32
33 /* Define to 1 to enable the context stack. */
34 #define CONTEXTUAL 0
35
36 struct lockdef
37 {
38 char const *login;
39 char const *revno;
40 };
41
42 struct notyet
43 {
44 char const *revno;
45 char const *next;
46 struct link *branches; /* list of ‘char const *’ */
47 struct delta *d;
48 };
49
50 struct grok
51 {
52 int c;
53 struct fro *from;
54 struct divvy *to; /* for caller */
55 struct divvy *systolic; /* internal */
56 struct divvy *tranquil; /* internal */
57 #if CONTEXTUAL
58 struct link *context;
59 size_t depth;
60 #endif /* CONTEXTUAL */
61 struct cbuf xrep;
62 size_t lno;
63 size_t head_lno;
64 struct cbuf bor_no; /* branch or revision */
65 };
66
67 #define STRUCTALLOC(to,type) alloc (to, sizeof (type))
68
69 #if CONTEXTUAL
70 static void
push_context(struct grok * g,char const * context)71 push_context (struct grok *g, char const *context)
72 {
73 g->context = prepend (context, g->context, g->systolic);
74 g->depth += 2;
75 }
76
77 static void
pop_context(struct grok * g)78 pop_context (struct grok *g)
79 {
80 struct link *bye = g->context;
81
82 g->depth -= 2;
83 g->context = g->context->next;
84 brush_off (g->systolic, bye);
85 }
86
87 #define CBEG(context) push_context (g, context)
88 #define CEND() pop_context (g)
89
90 #else /* !CONTEXTUAL */
91
92 #define CBEG(context)
93 #define CEND()
94
95 #endif
96
97 exiting
98 static void
ignoble(struct grok * g,char const * fmt,...)99 ignoble (struct grok *g, char const *fmt, ...)
100 {
101 va_list args;
102 struct cbuf msg;
103 struct divvy *scratch = g->systolic;
104 struct obstack *o = &scratch->space;
105
106 /* First, discard work-in-progress cruft. */
107 obstack_free (o, obstack_finish (o));
108
109 va_start (args, fmt);
110 obstack_vprintf (o, fmt, args);
111 va_end (args);
112
113 #if CONTEXTUAL
114 while (g->context)
115 {
116 accf (scratch, "\n from \"%s\"", g->context->entry);
117 g->context = g->context->next;
118 }
119 #endif /* CONTEXTUAL */
120 msg.string = finish_string (scratch, &msg.size);
121 complain ("\n");
122 fatal_syntax (g->lno, "%s", msg.string);
123 }
124
125 #define BUMMER(...) ignoble (g, __VA_ARGS__)
126
127 static void
eof_too_soon(struct grok * g)128 eof_too_soon (struct grok *g)
129 {
130 BUMMER ("unexpected end of file");
131 }
132
133 #define MORE(g) GETCHAR_OR (g->c, g->from, eof_too_soon (g))
134 #define XREP(g) ((g)->xrep)
135
136 static void
skip_whitespace(struct grok * g)137 skip_whitespace (struct grok *g)
138 {
139 for (;;)
140 {
141 if ('\n' == g->c)
142 g->lno++;
143 if (!isspace (g->c))
144 return;
145 MORE (g);
146 }
147 }
148
149 static void
must_read_keyword(struct grok * g,struct tinysym const * kw)150 must_read_keyword (struct grok *g, struct tinysym const *kw)
151 {
152 CBEG (TINYS (kw));
153 skip_whitespace (g);
154 for (size_t i = 0; i < kw->len; i++)
155 {
156 if (TINYS (kw)[i] != g->c)
157 BUMMER ("missing `%s' keyword", TINYS (kw));
158 MORE (g);
159 }
160 XREP (g).string = TINYS (kw);
161 XREP (g).size = kw->len;
162 CEND ();
163 }
164
165 #define SYNCH(g,kw) must_read_keyword (g, &TINY (kw))
166
167 static bool
probe_keyword(struct grok * g,struct tinysym const * kw)168 probe_keyword (struct grok *g, struct tinysym const *kw)
169 /* Return true if keyword ‘kw’ was found, else false. */
170 {
171 off_t was;
172 bool rv = true;
173
174 CBEG (TINYS (kw));
175 skip_whitespace (g);
176 was = fro_tello (g->from);
177 for (size_t i = 0; i < kw->len; i++)
178 {
179 if (! (rv = (TINYS (kw)[i] == g->c)))
180 break;
181 MORE (g);
182 }
183 if (rv)
184 {
185 XREP (g).string = TINYS (kw);
186 XREP (g).size = kw->len;
187 }
188 else
189 {
190 fro_move (g->from, was - 1);
191 MORE (g);
192 }
193 CEND ();
194 return rv;
195 }
196
197 static void
accb(struct grok * g)198 accb (struct grok *g)
199 {
200 accumulate_byte (g->to, g->c);
201 MORE (g);
202 }
203
204 static bool
maybe_read_num(struct grok * g,bool must_be_delta)205 maybe_read_num (struct grok *g, bool must_be_delta)
206 /* Return true and save to ‘to’ if a number was read,
207 else false. */
208 {
209 char *p;
210 size_t dots = 0;
211
212 CBEG ("num");
213 skip_whitespace (g);
214 while ('.' == g->c || isdigit (g->c))
215 {
216 if (must_be_delta)
217 dots += ('.' == g->c);
218 accb (g);
219 }
220 p = finish_string (g->to, &XREP (g).size);
221 CEND ();
222 if (XREP (g).size)
223 {
224 bool trailing_garbage = (';' != g->c && !isspace (g->c));
225
226 if (trailing_garbage)
227 {
228 accs (g->to, p);
229 while (';' != g->c && !isspace (g->c))
230 accb (g);
231 p = finish_string (g->to, &XREP (g).size);
232 }
233 if (trailing_garbage
234 || (must_be_delta
235 && EVENP (dots)))
236 BUMMER ("invalid %s: %s", ks_revno, p);
237 XREP (g).string = p;
238 return true;
239 }
240 brush_off (g->to, p);
241 XREP (g).string = "";
242 return false;
243 }
244
245 static void
must_read_num(struct grok * g,char const * role)246 must_read_num (struct grok *g, char const *role)
247 {
248 if (! maybe_read_num (g, ks_revno == role))
249 BUMMER ("missing %s", role);
250 }
251
252 #define MUST_REVNO(g) must_read_num (g, ks_revno)
253 #define MAYBE_REVNO(g) maybe_read_num (g, true)
254
255 static bool
maybe_read_snippet(struct grok * g)256 maybe_read_snippet (struct grok *g)
257 {
258 char *p;
259
260 CBEG ("snippet");
261 skip_whitespace (g);
262 while (';' != g->c
263 && ':' != g->c
264 && !isspace (g->c)
265 && UNKN != ctab[g->c])
266 accb (g);
267 p = finish_string (g->to, &XREP (g).size);
268 CEND ();
269 if (XREP (g).size)
270 {
271 XREP (g).string = p;
272 return true;
273 }
274 brush_off (g->to, p);
275 XREP (g).string = "";
276 return false;
277 }
278
279 static void
must_read_snippet(struct grok * g,char const * role)280 must_read_snippet (struct grok *g, char const *role)
281 {
282 if (! maybe_read_snippet (g))
283 BUMMER ("missing %s", role);
284 }
285
286 #define MUST_SNIPPET(g,kw) must_read_snippet (g, TINYKS (kw))
287
288 #define MASK_OFFMSB (1ULL << (sizeof (off_t) * 8 - 1))
289
290 static void
start_atat(struct divvy * to,bool blank)291 start_atat (struct divvy *to, bool blank)
292 {
293 struct obstack *o = &to->space;
294
295 /* We used to check and ensure ‘off_t’ (8-byte) alignment here,
296 but that is now done in ‘make_space’ directly. */
297
298 if (blank)
299 obstack_blank (o, sizeof (struct atat));
300 }
301
302 static struct atat *
finish_atat(struct divvy * to)303 finish_atat (struct divvy *to)
304 {
305 struct atat *rv;
306 struct obstack *o = &to->space;
307 size_t hsize = obstack_object_size (o) - sizeof (struct atat);
308
309 rv = obstack_finish (o);
310 rv->count = hsize / sizeof (off_t);
311 return rv;
312 }
313
314 #define BITPOSMOD64(i) (1ULL << (i % 64))
315
316 #if WITH_NEEDEXP
317
318 static bool
atat_ineedexp_many(struct atat * atat,size_t i)319 atat_ineedexp_many (struct atat *atat, size_t i)
320 {
321 return BITPOSMOD64 (i) & atat->needexp.bitset[i / 64];
322 }
323
324 static bool
atat_ineedexp_few(struct atat * atat,size_t i)325 atat_ineedexp_few (struct atat *atat, size_t i)
326 {
327 return BITPOSMOD64 (i) & atat->needexp.direct;
328 }
329
330 #endif /* WITH_NEEDEXP */
331
332 #define SETBIT(i,word) word |= BITPOSMOD64 (i)
333
334 #define MANYP(atat,x) ((8 * sizeof (atat->needexp.direct)) <= (x))
335
336 static bool
maybe_read_atat(struct grok * g,struct atat ** res)337 maybe_read_atat (struct grok *g, struct atat **res)
338 {
339 struct atat *atat = NULL;
340 off_t beg;
341 size_t lno_start;
342 bool newline = false;
343
344 #define POS(adjust) (fro_tello (g->from) + (adjust))
345
346 CBEG ("atat");
347 skip_whitespace (g);
348
349 /* Don't bother constructing an empty (length zero) atat
350 if there is nothing to do. */
351 if (SDELIM != g->c)
352 goto done;
353
354 lno_start = g->lno;
355 beg = POS (-1);
356 start_atat (g->systolic, true);
357 while (SDELIM == g->c)
358 {
359 off_t hole;
360 bool needexp = false;
361
362 MORE (g);
363 while (SDELIM != g->c)
364 {
365 if (WITH_NEEDEXP && KDELIM == g->c)
366 needexp = true;
367 else if ((newline = ('\n' == g->c)))
368 g->lno++;
369 MORE (g);
370 }
371 MORE (g);
372 hole = (needexp ? MASK_OFFMSB : 0) | POS (SDELIM == g->c ? -1 : -2);
373 obstack_grow (&g->systolic->space, &hole, sizeof (hole));
374 }
375 if ((atat = finish_atat (g->systolic)))
376 {
377 size_t count = atat->count;
378 #if WITH_NEEDEXP
379 bool many;
380
381 atat->needexp_count = 0;
382 if ((many = MANYP (atat, count)))
383 atat->needexp.bitset =
384 zlloc (g->to,
385 (1 + count / 64) * sizeof (*atat->needexp.bitset));
386 else
387 atat->needexp.direct = 0;
388 atat->ineedexp = many
389 ? atat_ineedexp_many
390 : atat_ineedexp_few;
391 for (size_t i = 0; i < count; i++)
392 if (MASK_OFFMSB & atat->holes[i])
393 {
394 if (many)
395 SETBIT (i, atat->needexp.bitset[i / 64]);
396 else
397 SETBIT (i, atat->needexp.direct);
398 atat->needexp_count++;
399 atat->holes[i] &= ~MASK_OFFMSB;
400 }
401 #endif /* WITH_NEEDEXP */
402
403 atat->lno = lno_start;
404 atat->line_count = g->lno - atat->lno + !newline;
405 atat->beg = beg;
406 atat->from = g->from;
407
408 /* Allocate a copy. FIXME: Should not be necessary!
409 (Unfortunately, attempts to save to ‘g->to’ directly resulted in a
410 segfault traced to ‘gmtime_r’ corrupting the hash table! Weird!) */
411 start_atat (g->to, false);
412 *res = obstack_copy (&g->to->space,
413 atat, (sizeof (struct atat)
414 + count * sizeof (off_t)));
415 }
416
417 done:
418 CEND ();
419 return BOOLEAN (atat);
420 }
421
422 static void
must_read_atat(struct grok * g,struct atat ** res,char const * role)423 must_read_atat (struct grok *g, struct atat **res, char const *role)
424 {
425 if (! maybe_read_atat (g, res))
426 BUMMER ("missing string after %s", role);
427 }
428
429 #define MUST_ATAT(g,res,kw) must_read_atat (g, res, TINYKS (kw))
430
431 static void
must_colon_revno(struct grok * g,char const * role)432 must_colon_revno (struct grok *g, char const *role)
433 {
434 CBEG (role);
435 CBEG ("colon");
436 /* NB: Don't skip whitespace. */
437 if (':' != g->c)
438 BUMMER ("missing ':' in %s", role);
439 MORE (g);
440 CEND ();
441 must_read_num (g, g->bor_no.string);
442 CEND ();
443 }
444
445 static void
must_semi(struct grok * g,char const * clause)446 must_semi (struct grok *g, char const *clause)
447 {
448 skip_whitespace (g);
449 if (';' != g->c)
450 BUMMER ("missing semicolon after `%s'", clause);
451 MORE (g);
452 }
453
454 #define SEMI(g,kw) must_semi (g, TINYKS (kw))
455
456
457 struct hash
458 {
459 size_t sz;
460 struct wlink **a;
461 };
462
463 static struct hash *
make_hash_table(struct divvy * to,size_t sz)464 make_hash_table (struct divvy *to, size_t sz)
465 {
466 struct hash *ht = alloc (to, sizeof (struct hash));
467
468 ht->sz = sz;
469 ht->a = zlloc (to, sz * sizeof (struct wlink *));
470 return ht;
471 }
472
473 static size_t
hash(char const * key,struct hash * ht)474 hash (char const *key, struct hash *ht)
475 {
476 return hash_pjw (key, ht->sz);
477 }
478
479 static void
puthash(struct divvy * to,struct notyet * ny,struct hash * ht)480 puthash (struct divvy *to, struct notyet *ny, struct hash *ht)
481 {
482 size_t slot = hash (ny->revno, ht);
483 struct wlink box, *tp, *cur;
484
485 box.next = ht->a[slot];
486 tp = &box;
487 while ((cur = tp->next))
488 {
489 struct notyet *maybe = cur->entry;
490
491 if (STR_SAME (ny->revno, maybe->revno))
492 {
493 tp->entry = ny;
494 return;
495 }
496 tp = tp->next;
497 }
498 tp = wextend (tp, ny, to);
499 ht->a[slot] = box.next;
500 }
501
502 static void *
gethash(char const * revno,struct hash * ht)503 gethash (char const *revno, struct hash *ht)
504 {
505 size_t slot = hash (revno, ht);
506
507 for (struct wlink *ls = ht->a[slot]; ls; ls = ls->next)
508 {
509 struct notyet *ny = ls->entry;
510
511 if (STR_SAME (revno, ny->revno))
512 return ny;
513 }
514 return NULL;
515 }
516
517
518 struct fwref
519 {
520 char const *revno;
521 size_t lno; /* zeroed on match */
522 };
523
524 struct repo *
empty_repo(struct divvy * to)525 empty_repo (struct divvy *to)
526 {
527 struct repo *repo = zlloc (to, sizeof (struct repo));
528
529 repo->strict = STRICT_LOCKING;
530 repo->expand = -1;
531 repo->neck = -1;
532 return repo;
533 }
534
535 /* A nice prime of monolithic proportions. */
536 #define NSLOTS 149
537
538 static const char ks_ner[] = "non-existent revision";
539
540 #define FIND_NY(revno) gethash (revno, repo->ht)
541
542 static struct repo *
full(struct divvy * to,struct fro * f)543 full (struct divvy *to, struct fro *f)
544 {
545 off_t neck;
546 size_t count;
547 struct link box, *tp;
548 struct wlink *follow;
549 struct wlink *all_br = NULL;
550 struct grok *g = FZLLOC (struct grok);
551 struct repo *repo = empty_repo (to);
552
553 repo->ht = make_hash_table (to, NSLOTS);
554
555 g->from = f;
556 g->to = to;
557 g->systolic = make_space ("systolic");
558 g->tranquil = make_space ("tranquil");
559 g->lno = 1;
560 accf (g->tranquil, "branch or %s", ks_revno);
561 g->bor_no.string = finish_string (g->tranquil, &g->bor_no.size);
562 MORE (g);
563
564 #define STASH(cvar) cvar = XREP (g).string
565 #define PREP(field) count = 0, box.next = repo->field, tp = &box
566 #define HANG(x) tp = extend (tp, x, to)
567 #define DONE(field) repo->field = box.next, repo->field ## _count = count
568
569 CBEG ("admin node");
570
571 SYNCH (g, head);
572 if (MAYBE_REVNO (g))
573 g->head_lno = g->lno, STASH (repo->head);
574 SEMI (g, head);
575
576 if (probe_keyword (g, &TINY (branch)))
577 {
578 if (maybe_read_num (g, false))
579 STASH (repo->branch);
580 SEMI (g, branch);
581 }
582
583 SYNCH (g, access);
584 for (PREP (access); maybe_read_snippet (g); count++)
585 HANG (XREP (g).string);
586 DONE (access);
587 SEMI (g, access);
588
589 SYNCH (g, symbols);
590 for (PREP (symbols); maybe_read_snippet (g); count++)
591 {
592 struct symdef *sym = STRUCTALLOC (to, struct symdef);
593
594 STASH (sym->meaningful);
595 must_colon_revno (g, "symbolic name definition");
596 STASH (sym->underlying);
597 HANG (sym);
598 }
599 DONE (symbols);
600 SEMI (g, symbols);
601
602 SYNCH (g, locks);
603 for (PREP (locks); maybe_read_snippet (g); count++)
604 {
605 struct lockdef *lock = STRUCTALLOC (to, struct lockdef);
606
607 STASH (lock->login);
608 must_colon_revno (g, "locker definition");
609 STASH (lock->revno);
610 HANG (lock);
611 }
612 DONE (locks);
613 SEMI (g, locks);
614 /* ci(1) might call ‘grok_resynch’, after having possibly deleted a lock
615 (with a side-effecting ‘setcdr’) so we save a copy of the definitions.
616 FIXME: Don't ‘setcdr’! (Alternatively, remove need to resynch.) */
617 repo->lockdefs = alloc (to,
618 count * sizeof (struct lockdef));
619 for (tp = repo->locks; count--; tp = tp->next)
620 {
621 struct lockdef const *orig = tp->entry;
622
623 repo->lockdefs[count] = *orig;
624 }
625
626 if ((repo->strict = probe_keyword (g, &TINY (strict))))
627 SEMI (g, strict);
628
629 if (probe_keyword (g, &TINY (integrity)))
630 {
631 if (maybe_read_atat (g, &repo->integrity)
632 && 1 < repo->integrity->count)
633 BUMMER ("spurious '@' in `%s' value", TINYKS (integrity));
634 SEMI (g, integrity);
635 }
636
637 if (probe_keyword (g, &TINY (comment)))
638 {
639 maybe_read_atat (g, &repo->comment);
640 SEMI (g, comment);
641 }
642
643 repo->expand = -1;
644 if (probe_keyword (g, &TINY (expand)))
645 {
646 struct atat *expand;
647
648 if (maybe_read_atat (g, &expand))
649 {
650 struct cbuf cb = string_from_atat (g->systolic, expand);
651
652 if (PROB (repo->expand = recognize_kwsub (&cb)))
653 BUMMER ("invalid expand mode: %s", cb.string);
654 }
655 SEMI (g, expand);
656 }
657
658 CBEG ("revisions");
659 {
660 struct wlink wbox, *wtp;
661 struct notyet *prev = NULL;
662 struct fwref *fw;
663
664 for (count = 0, wbox.next = repo->deltas, wtp = &wbox;
665 MAYBE_REVNO (g);
666 count++)
667 {
668 struct notyet *ny = STRUCTALLOC (to, struct notyet);
669 struct delta *d = ny->d = STRUCTALLOC (to, struct delta);
670 size_t numlen = XREP (g).size;
671
672 STASH (d->num);
673 /* Check that a new branch is properly forward-referenced. */
674 if (prev && !prev->next
675 && 2 <= countnumflds (d->num))
676 {
677 struct wlink *ls;
678
679 for (ls = all_br; ls; ls = ls->next)
680 if ((fw = ls->entry)->lno
681 && STR_SAME (d->num, fw->revno))
682 {
683 fw->lno = 0;
684 break;
685 }
686 if (!ls)
687 BUMMER ("unexpected new branch %s: %s", ks_revno, d->num);
688 }
689
690 d->branches = NULL; /* see ‘grok_all’ */
691 d->ilk = NULL; /* see ‘grok_all’ */
692 d->lockedby = NULL; /* see ‘grok_resynch’ */
693 d->pretty_log.string = NULL;
694 d->pretty_log.size = 0;
695 d->selector = true;
696 d->log = NULL;
697
698 STASH (ny->revno);
699 CBEG (ny->revno);
700
701 SYNCH (g, date);
702 must_read_num (g, "date");
703 STASH (d->date);
704 SEMI (g, date);
705
706 SYNCH (g, author);
707 MUST_SNIPPET (g, author);
708 STASH (d->author);
709 SEMI (g, author);
710
711 SYNCH (g, state);
712 MUST_SNIPPET (g, state);
713 STASH (d->state);
714 SEMI (g, state);
715
716 SYNCH (g, branches);
717 box.next = NULL, tp = &box;
718 while (MAYBE_REVNO (g))
719 {
720 const char *gs = XREP (g).string;
721
722 /* Branches must begin with the branch point revision. */
723 if (numlen >= XREP (g).size
724 || strncmp (d->num, gs, numlen)
725 || '.' != gs[numlen]
726 || 2 != countnumflds (gs + numlen + 1))
727 BUMMER ("invalid branch `%s' at branchpoint `%s'",
728 gs, d->num);
729 fw = STRUCTALLOC (g->tranquil, struct fwref);
730 fw->revno = gs;
731 fw->lno = g->lno;
732 all_br = wprepend (fw, all_br, g->tranquil);
733 HANG (gs);
734 }
735 ny->branches = box.next;
736 SEMI (g, branches);
737
738 SYNCH (g, next);
739 if (MAYBE_REVNO (g))
740 STASH (ny->next);
741 else
742 ny->next = NULL;
743 SEMI (g, next);
744
745 if (probe_keyword (g, &TINY (commitid)))
746 {
747 MUST_SNIPPET (g, commitid);
748 STASH (d->commitid);
749 checkssym (d->commitid);
750 SEMI (g, commitid);
751 }
752 else
753 d->commitid = NULL;
754
755 CEND ();
756 wtp = wextend (wtp, ny, to);
757 puthash (to, ny, repo->ht);
758 prev = ny;
759 }
760 /* Check that all forward references were matched. */
761 for (; all_br; all_br = all_br->next)
762 if ((fw = all_br->entry)->lno)
763 {
764 /* Jam line number for error message. */
765 g->lno = fw->lno;
766 BUMMER ("branch refers to %s `%s'", ks_ner, fw->revno);
767 }
768 repo->deltas = wbox.next;
769 repo->deltas_count = count;
770 }
771 CEND ();
772
773 SYNCH (g, desc);
774 repo->neck = fro_tello (g->from);
775 MUST_ATAT (g, &repo->desc, desc);
776
777 CEND ();
778
779 #undef EXTEND
780 #undef PREP
781 #undef STASH
782
783 /* Handle dangling lockdefs, that is, those whose revno references a
784 non-existent delta. Do it here instead of lazily (in ‘grok_resynch’)
785 to avoid emitting multiple warnings. */
786 for (struct lockdef const *lock = repo->lockdefs;
787 lock < repo->lockdefs + repo->locks_count;
788 lock++)
789 {
790 struct notyet *ny = FIND_NY (lock->revno);
791
792 if (! ny)
793 /* Create a dummy, hashed but not added to ‘repo->deltas’. */
794 {
795 RWARN ("user `%s' holds a lock for %s `%s'",
796 lock->login, ks_ner, lock->revno);
797 ny = zlloc (to, sizeof (struct notyet));
798 ny->d = zlloc (to, sizeof (struct delta));
799 ny->revno = ny->d->num = lock->revno;
800 puthash (to, ny, repo->ht);
801 }
802 }
803
804 CBEG ("edits");
805 for (count = 0, follow = repo->deltas;
806 (neck = fro_tello (g->from)) && count < repo->deltas_count;
807 count++)
808 {
809 char const *revno;
810 struct notyet *ny;
811 struct delta *d;
812
813 MUST_REVNO (g);
814 revno = XREP (g).string;
815 CBEG (revno);
816 if (!(ny = FIND_NY (revno)))
817 BUMMER ("found edits for %s `%s'", ks_ner, revno);
818 /* TODO: Instead of this (accumulate then reorder),
819 delay accumulation then accumulate in order. */
820 follow->entry = ny;
821 follow = follow->next;
822 d = ny->d;
823 if (d->log)
824 BUMMER ("duplicate delta log for %s `%s'", ks_revno, d->num);
825 d->neck = neck;
826 SYNCH (g, log);
827 MUST_ATAT (g, &d->log, log);
828 SYNCH (g, text);
829 MUST_ATAT (g, &d->text, text);
830 CEND ();
831 }
832 CEND ();
833
834 /* Yes, this is what teenagers learn painting
835 houses in the summertime. Recommended!
836 | en: dingleberry
837 | it: tarzanello
838 | es: gamborimbo
839 | de: Klabusterbeere
840 | no: Danglebær
841 | sv: kånkelbär
842 | lt: sudgabalis
843 Thanks wiktionary.org! */
844 CBEG ("clean tail");
845 while (isspace (g->c))
846 {
847 if ('\n' == g->c)
848 g->lno++;
849 GETCHAR_OR (g->c, g->from, goto ok);
850 }
851 BUMMER ("junk at end of file: '%c'", g->c);
852 ok:
853 CEND ();
854
855 /* Validate ‘GROK (head)’. */
856 if (repo->head && !FIND_NY (repo->head))
857 fatal_syntax (g->head_lno, "RCS file head names a %s `%s'",
858 ks_ner, repo->head);
859
860 /* Link deltas (via ‘branches’ and ‘next’). */
861 for (struct wlink *ls = repo->deltas; ls; ls = ls->next)
862 {
863 struct notyet *ny = ls->entry, *deref;
864 struct delta *d = ny->d;
865
866 #define FIND_D(revno) ((deref = FIND_NY (revno))->d)
867
868 if (ny->next)
869 d->ilk = FIND_D (ny->next);
870 if (ny->branches)
871 {
872 struct link *bls;
873 struct wlink wbox, *wtp;
874
875 for (bls = ny->branches, wbox.next = d->branches, wtp = &wbox;
876 bls;
877 bls = bls->next)
878 wtp = wextend (wtp, FIND_D (bls->entry), to);
879 d->branches = wbox.next;
880 }
881 ls->entry = d;
882
883 #undef FIND_D
884 }
885
886 /* Don't close ‘g->to’; that is caller's responsability. */
887 close_space (g->systolic);
888 close_space (g->tranquil);
889
890 return repo;
891 }
892
893 struct repo *
grok_all(struct divvy * to,struct fro * f)894 grok_all (struct divvy *to, struct fro *f)
895 {
896 struct repo *repo = full (to, f);
897
898 grok_resynch (repo);
899 return repo;
900 }
901
902 void
grok_resynch(struct repo * repo)903 grok_resynch (struct repo *repo)
904 /* (Re-)initialize the appropriate global variables. */
905 {
906 struct notyet *ny;
907
908 REPO (tip) = repo->head && (ny = FIND_NY (repo->head))
909 ? ny->d
910 : NULL;
911
912 repo->locks = NULL;
913 for (struct lockdef const *orig = repo->lockdefs + repo->locks_count;
914 repo->lockdefs < orig-- && (ny = FIND_NY (orig->revno));)
915 {
916 struct delta *d = ny->d;
917 struct rcslock *rl = FALLOC (struct rcslock);
918
919 rl->login = d->lockedby = orig->login;
920 rl->delta = d;
921 repo->locks = prepend (rl, repo->locks, SINGLE);
922 }
923
924 BE (strictly_locking) = repo->strict;
925
926 if (repo->comment)
927 REPO (log_lead) = string_from_atat (SINGLE, repo->comment);
928 else
929 clear_buf (&REPO (log_lead));
930
931 BE (kws) = PROB (repo->expand)
932 ? kwsub_kv
933 : repo->expand;
934 }
935
936 /* b-grok.c ends here */
937