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