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