1 /* Handle RCS revision numbers.
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 <ctype.h>
26 #include "b-complain.h"
27 #include "b-divvy.h"
28 #include "b-esds.h"
29 
30 static int
split(char const * s,char const ** lastdot)31 split (char const *s, char const **lastdot)
32 /* Given a pointer ‘s’ to a dotted number (date or revision number),
33    return the number of fields in ‘s’, and set ‘*lastdot’ to point
34    to the last '.' (or NULL if there is none).  */
35 {
36   size_t count;
37 
38   *lastdot = NULL;
39   if (!s || !*s)
40     return 0;
41   count = 1;
42   do
43     {
44       if (*s++ == '.')
45         {
46           *lastdot = s - 1;
47           count++;
48         }
49     }
50   while (*s);
51   return count;
52 }
53 
54 int
countnumflds(char const * s)55 countnumflds (char const *s)
56 /* Given a pointer ‘s’ to a dotted number (date or revision number),
57    return the number of digitfields in ‘s’.  */
58 {
59   register char const *sp;
60   register int count;
61 
62   if (!(sp = s) || !*sp)
63     return 0;
64   count = 1;
65   do
66     {
67       if (*sp++ == '.')
68         count++;
69     }
70   while (*sp);
71   return (count);
72 }
73 
74 static void
accumulate_branchno(struct divvy * space,char const * revno)75 accumulate_branchno (struct divvy *space, char const *revno)
76 {
77   char const *end;
78   int nfields = split (revno, &end);
79 
80   if (ODDP (nfields))
81     accs (space, revno);
82   else
83     accumulate_range (space, revno, end);
84 }
85 
86 struct cbuf
take(size_t count,char const * ref)87 take (size_t count, char const *ref)
88 /* Copy ‘count’ fields of ‘ref’ (branch or revision number) into ‘SINGLE’.
89    If ‘count’ is zero, take it to be the number of fields required to
90    return the branch number of ‘ref’.  Return the new string.  */
91 {
92   struct cbuf rv;
93   char const *end = ref;
94 
95   if (!count)
96     count = -2 + (1U | (1 + countnumflds (ref)));
97 
98   while (count--)
99     while (*end && '.' != *end++)
100       continue;
101 
102   accumulate_range (SINGLE, ref, *end ? end - 1 : end);
103   rv.string = finish_string (SINGLE, &rv.size);
104   return rv;
105 }
106 
107 int
cmpnum(char const * num1,char const * num2)108 cmpnum (char const *num1, char const *num2)
109 /* Compare the two dotted numbers ‘num1’ and ‘num2’ lexicographically
110    by field.  Individual fields are compared numerically.
111    Return <0, 0, >0 if ‘num1 < num2’, ‘num1 == num2’, ‘num1 > num2’,
112    respectively.  Omitted fields are assumed to be higher than the existing
113    ones.  */
114 {
115   register char const *s1, *s2;
116   register size_t d1, d2;
117   register int r;
118 
119   s1 = num1 ? num1 : "";
120   s2 = num2 ? num2 : "";
121 
122   for (;;)
123     {
124       /* Give precedence to shorter one.  */
125       if (!*s1)
126         return (unsigned char) *s2;
127       if (!*s2)
128         return -1;
129 
130       /* Strip leading zeros, then find number of digits.  */
131       while (*s1 == '0')
132         ++s1;
133       while (*s2 == '0')
134         ++s2;
135       for (d1 = 0; isdigit (*(s1 + d1)); d1++)
136         continue;
137       for (d2 = 0; isdigit (*(s2 + d2)); d2++)
138         continue;
139 
140       /* Do not convert to integer; it might overflow!  */
141       if (d1 != d2)
142         return d1 < d2 ? -1 : 1;
143       if ((r = memcmp (s1, s2, d1)))
144         return r;
145       s1 += d1;
146       s2 += d1;
147 
148       /* Skip '.'.  */
149       if (*s1)
150         s1++;
151       if (*s2)
152         s2++;
153     }
154 }
155 
156 int
cmpnumfld(char const * num1,char const * num2,int fld)157 cmpnumfld (char const *num1, char const *num2, int fld)
158 /* Compare the two dotted numbers at field ‘fld’.
159    ‘num1’ and ‘num2’ must have at least ‘fld’ fields.
160    ‘fld’ must be positive.  */
161 {
162   register char const *s1, *s2;
163   register size_t d1, d2;
164 
165   s1 = num1;
166   s2 = num2;
167   /* Skip ‘fld - 1’ fields.  */
168   while (--fld)
169     {
170       while (*s1++ != '.')
171         continue;
172       while (*s2++ != '.')
173         continue;
174     }
175   /* Now ‘s1’ and ‘s2’ point to the beginning of the respective fields.  */
176   while (*s1 == '0')
177     ++s1;
178   for (d1 = 0; isdigit (*(s1 + d1)); d1++)
179     continue;
180   while (*s2 == '0')
181     ++s2;
182   for (d2 = 0; isdigit (*(s2 + d2)); d2++)
183     continue;
184 
185   return d1 < d2 ? -1 : d1 == d2
186     ? memcmp (s1, s2, d1)
187     : 1;
188 }
189 
190 static char const *
normalizeyear(char const * date,char year[5])191 normalizeyear (char const *date, char year[5])
192 {
193   if (isdigit (date[0]) && isdigit (date[1]) && !isdigit (date[2]))
194     {
195       year[0] = '1';
196       year[1] = '9';
197       year[2] = date[0];
198       year[3] = date[1];
199       year[4] = 0;
200       return year;
201     }
202   else
203     return date;
204 }
205 
206 int
cmpdate(char const * d1,char const * d2)207 cmpdate (char const *d1, char const *d2)
208 /* Compare the two dates.  This is just like ‘cmpnum’,
209    except that for compatibility with old versions of RCS,
210    1900 is added to dates with two-digit years.  */
211 {
212   char year1[5], year2[5];
213   int r = cmpnumfld (normalizeyear (d1, year1), normalizeyear (d2, year2), 1);
214 
215   if (r)
216     return r;
217   else
218     {
219       while (isdigit (*d1))
220         d1++;
221       d1 += *d1 == '.';
222       while (isdigit (*d2))
223         d2++;
224       d2 += *d2 == '.';
225       return cmpnum (d1, d2);
226     }
227 }
228 
229 static void
cantfindbranch(char const * revno,char const date[DATESIZE],char const * author,char const * state)230 cantfindbranch (char const *revno, char const date[DATESIZE],
231                 char const *author, char const *state)
232 {
233   char datebuf[FULLDATESIZE];
234 
235   RERR ("No revision on branch %s has%s%s%s%s%s%s.",
236         revno,
237         date ? " a date before " : "",
238         date ? date2str (date, datebuf) : "",
239         author ? " and author " + (date ? 0 : 4) : "",
240         author ? author : "",
241         state ? " and state " + (date || author ? 0 : 4) : "",
242         state ? state : "");
243 }
244 
245 static void
absent(char const * revno,int field)246 absent (char const *revno, int field)
247 {
248   RERR ("%s %s absent",
249         ODDP (field) ? "revision" : "branch",
250         TAKE (field, revno));
251 }
252 
253 int
compartial(char const * num1,char const * num2,int length)254 compartial (char const *num1, char const *num2, int length)
255 /* Compare the first ‘length’ fields of two dot numbers;
256    the omitted field is considered to be larger than any number.
257    Restriction: At least one number has ‘length’ or more fields.  */
258 {
259   register char const *s1, *s2;
260   register size_t d1, d2;
261   register int r;
262 
263   s1 = num1;
264   s2 = num2;
265   if (!s1)
266     return 1;
267   if (!s2)
268     return -1;
269 
270   for (;;)
271     {
272       if (!*s1)
273         return 1;
274       if (!*s2)
275         return -1;
276 
277       while (*s1 == '0')
278         ++s1;
279       for (d1 = 0; isdigit (*(s1 + d1)); d1++)
280         continue;
281       while (*s2 == '0')
282         ++s2;
283       for (d2 = 0; isdigit (*(s2 + d2)); d2++)
284         continue;
285 
286       if (d1 != d2)
287         return d1 < d2 ? -1 : 1;
288       if ((r = memcmp (s1, s2, d1)))
289         return r;
290       if (!--length)
291         return 0;
292 
293       s1 += d1;
294       s2 += d1;
295 
296       if (*s1 == '.')
297         s1++;
298       if (*s2 == '.')
299         s2++;
300     }
301 }
302 
303 static void
store1(struct wlink *** store,struct delta * next)304 store1 (struct wlink ***store, struct delta *next)
305 /* Allocate a new list node that addresses ‘next’.
306    Append it to the list that ‘**store’ is the end pointer of.  */
307 {
308   register struct wlink *p;
309 
310   p = FALLOC (struct wlink);
311   /* Note: We don't clear ‘p->next’ here;
312      ‘CLEAR_MAYBE’ does that (after looping).  */
313   p->entry = next;
314   **store = p;
315   *store = &p->next;
316 }
317 
318 #define STORE_MAYBE(x)  if (store) store1 (&store, x)
319 #define CLEAR_MAYBE()   if (store) *store = NULL
320 
321 static struct delta *
genbranch(struct delta const * bpoint,char const * revno,int length,char const * date,char const * author,char const * state,struct wlink ** store)322 genbranch (struct delta const *bpoint, char const *revno,
323            int length, char const *date, char const *author,
324            char const *state, struct wlink **store)
325 /* Given a branchpoint, a revision number, date, author, and state, find the
326    deltas necessary to reconstruct the given revision from the branch point
327    on.  If ‘store’ is non-NULL, pointers to the found deltas are stored
328    in a list beginning with ‘store’.  ‘revno’ must be on a side branch.
329    Return NULL on error.  */
330 {
331   int field;
332   register struct delta *d, *trail;
333   register struct wlink const *bhead;
334   int result;
335   char datebuf[FULLDATESIZE];
336 
337   field = 3;
338   bhead = bpoint->branches;
339 
340   do
341     {
342       if (!bhead)
343         {
344           RERR ("no side branches present for %s", TAKE (field - 1, revno));
345           return NULL;
346         }
347 
348       /* Find branch head.  Branches are arranged in increasing order.  */
349       while (d = bhead->entry,
350              0 < (result = cmpnumfld (revno, d->num, field)))
351         {
352           bhead = bhead->next;
353           if (!bhead)
354             {
355               RERR ("branch number %s too high", TAKE (field, revno));
356               return NULL;
357             }
358         }
359 
360       if (result < 0)
361         {
362           absent (revno, field);
363           return NULL;
364         }
365 
366       d = bhead->entry;
367       if (length == field)
368         {
369           /* Pick latest one on that branch.  */
370           trail = NULL;
371           do
372             {
373               if ((!date || !DATE_LT (date, d->date))
374                   && (!author || STR_SAME (author, d->author))
375                   && (!state || STR_SAME (state, d->state)))
376                 trail = d;
377               d = d->ilk;
378             }
379           while (d);
380 
381           if (!trail)
382             {
383               cantfindbranch (revno, date, author, state);
384               return NULL;
385             }
386           else
387             {
388               /* Print up to last one suitable.  */
389               d = bhead->entry;
390               while (d != trail)
391                 {
392                   STORE_MAYBE (d);
393                   d = d->ilk;
394                 }
395               STORE_MAYBE (d);
396             }
397           CLEAR_MAYBE ();
398           return d;
399         }
400 
401       /* Length > field.  Find revision.  Check low.  */
402       if (NUMF_LT (1 + field, revno, d->num))
403         {
404           RERR ("%s %s too low", ks_revno, TAKE (field + 1, revno));
405           return NULL;
406         }
407       do
408         {
409           STORE_MAYBE (d);
410           trail = d;
411           d = d->ilk;
412         }
413       while (d && !NUMF_LT (1 + field, revno, d->num));
414 
415       if ((length > field + 1)
416           /* Need exact hit.  */
417           && !NUMF_EQ (1 + field, revno, trail->num))
418         {
419           absent (revno, field + 1);
420           return NULL;
421         }
422       if (length == field + 1)
423         {
424           if (date && DATE_LT (date, trail->date))
425             {
426               RERR ("Revision %s has date %s.",
427                     trail->num, date2str (trail->date, datebuf));
428               return NULL;
429             }
430           if (author && STR_DIFF (author, trail->author))
431             {
432               RERR ("Revision %s has author %s.", trail->num, trail->author);
433               return NULL;
434             }
435           if (state && STR_DIFF (state, trail->state))
436             {
437               RERR ("Revision %s has state %s.",
438                     trail->num,
439                     trail->state ? trail->state : "<empty>");
440               return NULL;
441             }
442         }
443       bhead = trail->branches;
444     }
445   while ((field += 2) <= length);
446   CLEAR_MAYBE ();
447   return trail;
448 }
449 
450 struct delta *
genrevs(char const * revno,char const * date,char const * author,char const * state,struct wlink ** store)451 genrevs (char const *revno, char const *date, char const *author,
452          char const *state, struct wlink **store)
453 /* Find the deltas needed for reconstructing the revision given by ‘revno’,
454    ‘date’, ‘author’, and ‘state’, and stores pointers to these deltas into
455    a list whose starting address is given by ‘store’ (if non-NULL).
456    Return the last delta (target delta).
457    If the proper delta could not be found, return NULL.  */
458 {
459   int length;
460   register struct delta *d;
461   int result;
462   char const *branchnum;
463   char datebuf[FULLDATESIZE];
464 
465   if (!(d = REPO (tip)))
466     {
467       RERR ("RCS file empty");
468       goto norev;
469     }
470 
471   length = countnumflds (revno);
472 
473   if (length >= 1)
474     {
475       /* At least one field; find branch exactly.  */
476       while ((result = cmpnumfld (revno, d->num, 1)) < 0)
477         {
478           STORE_MAYBE (d);
479           d = d->ilk;
480           if (!d)
481             {
482               RERR ("branch number %s too low", TAKE (1, revno));
483               goto norev;
484             }
485         }
486 
487       if (result > 0)
488         {
489           absent (revno, 1);
490           goto norev;
491         }
492     }
493   if (length <= 1)
494     {
495       /* Pick latest one on given branch.  */
496       branchnum = d->num;               /* works even for empty revno */
497       while (d
498              && NUMF_EQ (1, branchnum, d->num)
499              && ((date && DATE_LT (date, d->date))
500                  || (author && STR_DIFF (author, d->author))
501                  || (state && STR_DIFF (state, d->state))))
502         {
503           STORE_MAYBE (d);
504           d = d->ilk;
505         }
506       if (!d || !NUMF_EQ (1, branchnum, d->num)) /* overshot */
507         {
508           cantfindbranch (length ? revno : TAKE (1, branchnum),
509                           date, author, state);
510           goto norev;
511         }
512       else
513         {
514           STORE_MAYBE (d);
515         }
516       CLEAR_MAYBE ();
517       return d;
518     }
519 
520   /* Length >= 2.  Find revision; may go low if ‘length == 2’.  */
521   while ((result = cmpnumfld (revno, d->num, 2)) < 0
522          && (NUMF_EQ (1, revno, d->num)))
523     {
524       STORE_MAYBE (d);
525       d = d->ilk;
526       if (!d)
527         break;
528     }
529 
530   if (!d || !NUMF_EQ (1, revno, d->num))
531     {
532       RERR ("%s %s too low", ks_revno, TAKE (2, revno));
533       goto norev;
534     }
535   if ((length > 2) && (result != 0))
536     {
537       absent (revno, 2);
538       goto norev;
539     }
540 
541   /* Print last one.  */
542   STORE_MAYBE (d);
543 
544   if (length > 2)
545     return genbranch (d, revno, length, date, author, state, store);
546   else
547     {                                   /* length == 2 */
548       if (date && DATE_LT (date, d->date))
549         {
550           RERR ("Revision %s has date %s.",
551                 d->num, date2str (d->date, datebuf));
552           return NULL;
553         }
554       if (author && STR_DIFF (author, d->author))
555         {
556           RERR ("Revision %s has author %s.", d->num, d->author);
557           return NULL;
558         }
559       if (state && STR_DIFF (state, d->state))
560         {
561           RERR ("Revision %s has state %s.",
562                 d->num, d->state ? d->state : "<empty>");
563           return NULL;
564         }
565       CLEAR_MAYBE ();
566       return d;
567     }
568 
569 norev:
570   return NULL;
571 }
572 
573 #undef CLEAR_MAYBE
574 #undef STORE_MAYBE
575 
576 struct delta *
gr_revno(char const * revno,struct wlink ** store)577 gr_revno (char const *revno, struct wlink **store)
578 /* An abbreviated form of ‘genrevs’, when you don't care
579    about the date, author, or state.  */
580 {
581   return genrevs (revno, NULL, NULL, NULL, store);
582 }
583 
584 struct delta *
delta_from_ref(char const * ref)585 delta_from_ref (char const *ref)
586 /* Return the hash entry associated with ‘ref’, a fully numeric
587    revision or branch number, or NULL if no such entry exists.  */
588 {
589   return genrevs (ref, NULL, NULL, NULL, NULL);
590 }
591 
592 static char const *
rev_from_symbol(struct cbuf const * id)593 rev_from_symbol (struct cbuf const *id)
594 /* Look up ‘id’ in the list of symbolic names starting with pointer
595    ‘GROK (symbols)’, and return a pointer to the corresponding
596    revision number.  Return NULL if not present.  */
597 {
598   for (struct link *ls = GROK (symbols); ls; ls = ls->next)
599     {
600       struct symdef const *d = ls->entry;
601 
602       if ('\0' == d->meaningful[id->size]
603           && !strncmp (d->meaningful, id->string, id->size))
604         return d->underlying;
605     }
606   return NULL;
607 }
608 
609 static char const *
lookupsym(char const * id)610 lookupsym (char const *id)
611 /* Look up ‘id’ in the list of symbolic names starting with pointer
612    ‘GROK (symbols)’, and return a pointer to the corresponding
613    revision number.  Return NULL if not present.  */
614 {
615   struct cbuf identifier =
616     {
617       .string = id,
618       .size = strlen (id)
619     };
620 
621   return rev_from_symbol (&identifier);
622 }
623 
624 static char const *
branchtip(char const * branch)625 branchtip (char const *branch)
626 {
627   struct delta *h;
628 
629   h = delta_from_ref (branch);
630   return h ? h->num : NULL;
631 }
632 
633 bool
fully_numeric(struct cbuf * ans,char const * source,struct fro * fp)634 fully_numeric (struct cbuf *ans, char const *source, struct fro *fp)
635 /* Expand ‘source’, pointing ‘ans’ at a new string in ‘SINGLE’,
636    with all symbolic fields replaced with their numeric values.
637    Expand a branch followed by ‘.’ to the latest revision on that branch.
638    Ignore ‘.’ after a revision.  Remove leading zeros.
639    If ‘fp’ is non-NULL, it is used to expand "$" (i.e., ‘KDELIM’).
640    Return true if successful, otherwise false.  */
641 {
642   register char const *sp, *bp = NULL;
643   int dots;
644   char *ugh = NULL;
645 
646 #define ACCF(...)  accf (SINGLE, __VA_ARGS__)
647 
648 #define FRESH()    if (ugh) brush_off (SINGLE, ugh)
649 #define ACCB(b)    accumulate_byte (SINGLE, b)
650 #define ACCS(s)    accs (SINGLE, s)
651 #define ACCR(b,e)  accumulate_range (SINGLE, b, e)
652 #define OK()       ugh = finish_string (SINGLE, &ans->size), ans->string = ugh
653 
654   sp = source;
655   if (!sp || !*sp)
656     /* Accept NULL pointer as a legal value.  */
657     goto success;
658   if (sp[0] == KDELIM && !sp[1])
659     {
660       if (!getoldkeys (fp))
661         goto sorry;
662       if (!PREV (rev))
663         {
664           MERR ("working file lacks %s", ks_revno);
665           goto sorry;
666         }
667       ACCS (PREV (rev));
668       goto success;
669     }
670   dots = 0;
671 
672   for (;;)
673     {
674       char const *was = sp;
675       bool id = false;
676 
677       for (;;)
678         {
679           switch (ctab[(unsigned char) *sp])
680             {
681             case IDCHAR:
682             case LETTER:
683             case Letter:
684               id = true;
685               /* fall into */
686             case DIGIT:
687               sp++;
688               continue;
689 
690             default:
691               break;
692             }
693           break;
694         }
695 
696       if (id)
697         {
698           struct cbuf orig =
699             {
700               .string = was,
701               .size = sp - was
702             };
703           char const *expanded = rev_from_symbol (&orig);
704 
705           if (!expanded)
706             {
707               RERR ("Symbolic name `%s' is undefined.", was);
708               goto sorry;
709             }
710           ACCS (expanded);
711         }
712       else
713         {
714           if (was != sp)
715             {
716               ACCR (was, sp);
717               bp = was;
718             }
719 
720           /* Skip leading zeros.  */
721           while ('0' == sp[0] && isdigit (sp[1]))
722             sp++;
723 
724           if (!bp)
725             {
726               int s = 0;                /* FAKE */
727               if (s || *sp != '.')
728                 break;
729               else
730                 {
731                   /* Insert default branch before initial ‘.’.  */
732                   char const *b;
733                   struct delta *tip;
734 
735                   if (GROK (branch))
736                     b = GROK (branch);
737                   else if ((tip = REPO (tip)))
738                     b = tip->num;
739                   else
740                     break;
741                   OK (); FRESH ();
742                   accumulate_branchno (SINGLE, b);
743                 }
744             }
745         }
746 
747       switch (*sp++)
748         {
749         case '\0':
750           goto success;
751 
752         case '.':
753           if (!*sp)
754             {
755               if (ODDP (dots))
756                 break;
757               OK ();
758               if (!(bp = branchtip (ans->string)))
759                 goto sorry;
760               /* Append only the non-branch part of the tip revision.  */
761               ACCF ("%s%s", ans->string, bp + ans->size);
762               goto success;
763             }
764           ++dots;
765           ACCB ('.');
766           continue;
767         }
768       break;
769     }
770 
771   RERR ("improper %s: %s", ks_revno, source);
772 
773  sorry:
774   OK ();
775   FRESH ();
776   return false;
777  success:
778   OK ();
779   return true;
780 
781 #undef OK
782 #undef ACCR
783 #undef ACCS
784 #undef ACCB
785 #undef FRESH
786 #undef ACCF
787 }
788 
789 char const *
namedrev(char const * name,struct delta * delta)790 namedrev (char const *name, struct delta *delta)
791 /* Return ‘name’ if it names ‘delta’, NULL otherwise.  */
792 {
793   if (name)
794     {
795       char const *id = NULL, *p, *val;
796 
797       for (p = name;; p++)
798         switch (ctab[(unsigned char) *p])
799           {
800           case IDCHAR:
801           case LETTER:
802           case Letter:
803             id = name;
804             break;
805 
806           case DIGIT:
807             break;
808 
809           case UNKN:
810             if (!*p && id
811                 && (val = lookupsym (id))
812                 && STR_SAME (val, delta->num))
813               return id;
814             /* fall into */
815           default:
816             return NULL;
817           }
818     }
819   return NULL;
820 }
821 
822 char const *
tiprev(void)823 tiprev (void)
824 {
825   struct delta *tip;
826   char const *defbr = GROK (branch);
827 
828   return defbr
829     ? branchtip (defbr)
830     : (tip = REPO (tip)) ? tip->num : NULL;
831 }
832 
833 /* rcsrev.c ends here */
834