1 /* Handle RCS revision numbers. */
3 /* Copyright 1982, 1988, 1989 Walter Tichy
4 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5 Distributed under license by the Free Software Foundation, Inc.
7 This file is part of RCS.
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 Report problems and direct all questions to:
26 rcs-bugs@cs.purdue.edu
31 * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.8 1999/08/27 23:36:48 peter Exp $
32 * $DragonFly: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.2 2003/06/17 04:25:47 dillon Exp $
34 * Revision 5.10 1995/06/16 06:19:24 eggert
37 * Revision 5.9 1995/06/01 16:23:43 eggert
38 * (cmpdate, normalizeyear): New functions work around MKS RCS incompatibility.
39 * (cmpnum, compartial): s[d] -> *(s+d) to work around Cray compiler bug.
40 * (genrevs, genbranch): cmpnum -> cmpdate
42 * Revision 5.8 1994/03/17 14:05:48 eggert
45 * Revision 5.7 1993/11/09 17:40:15 eggert
46 * Fix format string typos.
48 * Revision 5.6 1993/11/03 17:42:27 eggert
49 * Revision number `.N' now stands for `D.N', where D is the default branch.
50 * Add -z. Improve quality of diagnostics. Add `namedrev' for Name support.
52 * Revision 5.5 1992/07/28 16:12:44 eggert
53 * Identifiers may now start with a digit. Avoid `unsigned'.
55 * Revision 5.4 1992/01/06 02:42:34 eggert
56 * while (E) ; -> while (E) continue;
58 * Revision 5.3 1991/08/19 03:13:55 eggert
59 * Add `-r$', `-rB.'. Remove botches like `<now>' from messages. Tune.
61 * Revision 5.2 1991/04/21 11:58:28 eggert
64 * Revision 5.1 1991/02/25 07:12:43 eggert
65 * Avoid overflow when comparing revision numbers.
67 * Revision 5.0 1990/08/22 08:13:43 eggert
68 * Remove compile-time limits; use malloc instead.
69 * Ansify and Posixate. Tune.
70 * Remove possibility of an internal error. Remove lint.
72 * Revision 4.5 89/05/01 15:13:22 narten
73 * changed copyright header to reflect current distribution rules
75 * Revision 4.4 87/12/18 11:45:22 narten
76 * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
77 * since there's now a return value there with a value. (Guy Harris)
79 * Revision 4.3 87/10/18 10:38:42 narten
80 * Updating version numbers. Changes relative to version 1.1 actually
83 * Revision 1.3 87/09/24 14:00:37 narten
84 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
87 * Revision 1.2 87/03/27 14:22:37 jenkins
90 * Revision 4.1 83/03/25 21:10:45 wft
91 * Only changed $Header to $Id.
93 * Revision 3.4 82/12/04 13:24:08 wft
94 * Replaced getdelta() with gettree().
96 * Revision 3.3 82/11/28 21:33:15 wft
97 * fixed compartial() and compnum() for nil-parameters; fixed nils
98 * in error messages. Testprogram output shortenend.
100 * Revision 3.2 82/10/18 21:19:47 wft
101 * renamed compnum->cmpnum, compnumfld->cmpnumfld,
102 * numericrevno->numricrevno.
104 * Revision 3.1 82/10/11 19:46:09 wft
105 * changed expandsym() to check for source==nil; returns zero length string
111 libId(revId, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcsrev.c,v 1.2 2003/06/17 04:25:47 dillon Exp $")
113 static char const *branchtip P((char const*));
114 static char const *lookupsym P((char const*));
115 static char const *normalizeyear P((char const*,char[5]));
116 static struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**));
117 static void absent P((char const*,int));
118 static void cantfindbranch P((char const*,char const[datesize],char const*,char const*));
119 static void store1 P((struct hshentries***,struct hshentry*));
126 /* Given a pointer s to a dotted number (date or revision number),
127 * countnumflds returns the number of digitfields in s.
130 register char const *sp;
136 if (*sp++ == '.') count++;
142 getbranchno(revno,branchno)
144 struct buf *branchno;
145 /* Given a revision number revno, getbranchno copies the number of the branch
146 * on which revno is into branchno. If revno itself is a branch number,
147 * it is copied unchanged.
150 register int numflds;
153 bufscpy(branchno, revno);
154 numflds=countnumflds(revno);
155 if (!(numflds & 1)) {
156 tp = branchno->string;
166 int cmpnum(num1, num2)
167 char const *num1, *num2;
168 /* compares the two dotted numbers num1 and num2 lexicographically
169 * by field. Individual fields are compared numerically.
170 * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
171 * omitted fields are assumed to be higher than the existing ones.
174 register char const *s1, *s2;
175 register size_t d1, d2;
178 s1 = num1 ? num1 : "";
179 s2 = num2 ? num2 : "";
182 /* Give precedence to shorter one. */
184 return (unsigned char)*s2;
188 /* Strip leading zeros, then find number of digits. */
189 while (*s1=='0') ++s1;
190 while (*s2=='0') ++s2;
191 for (d1=0; isdigit(*(s1+d1)); d1++) continue;
192 for (d2=0; isdigit(*(s2+d2)); d2++) continue;
194 /* Do not convert to integer; it might overflow! */
196 return d1<d2 ? -1 : 1;
197 if ((r = memcmp(s1, s2, d1)))
210 int cmpnumfld(num1, num2, fld)
211 char const *num1, *num2;
213 /* Compare the two dotted numbers at field fld.
214 * num1 and num2 must have at least fld fields.
215 * fld must be positive.
218 register char const *s1, *s2;
219 register size_t d1, d2;
223 /* skip fld-1 fields */
230 /* Now s1 and s2 point to the beginning of the respective fields */
231 while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
232 while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
234 return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
242 * Compare the two dates. This is just like cmpnum,
243 * except that for compatibility with old versions of RCS,
244 * 1900 is added to dates with two-digit years.
247 char year1[5], year2[5];
248 int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1);
253 while (isdigit(*d1)) d1++; d1 += *d1=='.';
254 while (isdigit(*d2)) d2++; d2 += *d2=='.';
255 return cmpnum(d1, d2);
260 normalizeyear(date, year)
264 if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) {
277 cantfindbranch(revno, date, author, state)
278 char const *revno, date[datesize], *author, *state;
280 char datebuf[datesize + zonelenmax];
282 rcserror("No revision on branch %s has%s%s%s%s%s%s.",
284 date ? " a date before " : "",
285 date ? date2str(date,datebuf) : "",
286 author ? " and author "+(date?0:4) : "",
287 author ? author : "",
288 state ? " and state "+(date||author?0:4) : "",
300 rcserror("%s %s absent", field&1?"revision":"branch",
301 partialno(&t,revno,field)
308 compartial(num1, num2, length)
309 char const *num1, *num2;
312 /* compare the first "length" fields of two dot numbers;
313 the omitted field is considered to be larger than any number */
314 /* restriction: at least one number has length or more fields */
317 register char const *s1, *s2;
318 register size_t d1, d2;
321 s1 = num1; s2 = num2;
329 while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
330 while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
333 return d1<d2 ? -1 : 1;
334 if ((r = memcmp(s1, s2, d1)))
342 if (*s1 == '.') s1++;
343 if (*s2 == '.') s2++;
348 char * partialno(rev1,rev2,length)
352 /* Function: Copies length fields of revision number rev2 into rev1.
353 * Return rev1's string.
361 while (*r1!='.' && *r1)
366 /* eliminate last '.'*/
376 struct hshentries ***store;
377 struct hshentry *next;
379 * Allocate a new list node that addresses NEXT.
380 * Append it to the list that **STORE is the end pointer of.
383 register struct hshentries *p;
385 p = ftalloc(struct hshentries);
391 struct hshentry * genrevs(revno,date,author,state,store)
392 char const *revno, *date, *author, *state;
393 struct hshentries **store;
394 /* Function: finds the deltas needed for reconstructing the
395 * revision given by revno, date, author, and state, and stores pointers
396 * to these deltas into a list whose starting address is given by store.
397 * The last delta (target delta) is returned.
398 * If the proper delta could not be found, 0 is returned.
402 register struct hshentry * next;
404 char const *branchnum;
406 char datebuf[datesize + zonelenmax];
410 if (!(next = Head)) {
411 rcserror("RCS file empty");
415 length = countnumflds(revno);
418 /* at least one field; find branch exactly */
419 while ((result=cmpnumfld(revno,next->num,1)) < 0) {
420 store1(&store, next);
423 rcserror("branch number %s too low", partialno(&t,revno,1));
434 /* pick latest one on given branch */
435 branchnum = next->num; /* works even for empty revno*/
437 cmpnumfld(branchnum,next->num,1) == 0 &&
439 (date && cmpdate(date,next->date) < 0) ||
440 (author && strcmp(author,next->author) != 0) ||
441 (state && strcmp(state,next->state) != 0)
445 store1(&store, next);
449 (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
451 length ? revno : partialno(&t,branchnum,1),
456 store1(&store, next);
463 /* find revision; may go low if length==2*/
464 while ((result=cmpnumfld(revno,next->num,2)) < 0 &&
465 (cmpnumfld(revno,next->num,1)==0) ) {
466 store1(&store, next);
472 if (!next || cmpnumfld(revno,next->num,1) != 0) {
473 rcserror("revision number %s too low", partialno(&t,revno,2));
476 if ((length>2) && (result!=0)) {
482 store1(&store, next);
485 return genbranch(next,revno,length,date,author,state,store);
486 else { /* length == 2*/
487 if (date && cmpdate(date,next->date)<0) {
488 rcserror("Revision %s has date %s.",
490 date2str(next->date, datebuf)
494 if (author && strcmp(author,next->author)!=0) {
495 rcserror("Revision %s has author %s.",
496 next->num, next->author
500 if (state && strcmp(state,next->state)!=0) {
501 rcserror("Revision %s has state %s.",
503 next->state ? next->state : "<empty>"
519 static struct hshentry *
520 genbranch(bpoint, revno, length, date, author, state, store)
521 struct hshentry const *bpoint;
524 char const *date, *author, *state;
525 struct hshentries **store;
526 /* Function: given a branchpoint, a revision number, date, author, and state,
527 * genbranch finds the deltas necessary to reconstruct the given revision
528 * from the branch point on.
529 * Pointers to the found deltas are stored in a list beginning with store.
530 * revno must be on a side branch.
535 register struct hshentry * next, * trail;
536 register struct branchhead const *bhead;
539 char datebuf[datesize + zonelenmax];
542 bhead = bpoint->branches;
547 rcserror("no side branches present for %s",
548 partialno(&t,revno,field-1)
555 /*branches are arranged in increasing order*/
556 while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
557 bhead = bhead->nextbranch;
560 rcserror("branch number %s too high",
561 partialno(&t,revno,field)
569 absent(revno, field);
575 /* pick latest one on that branch */
577 do { if ((!date || cmpdate(date,next->date)>=0) &&
578 (!author || strcmp(author,next->author)==0) &&
579 (!state || strcmp(state,next->state)==0)
585 cantfindbranch(revno, date, author, state);
587 } else { /* print up to last one suitable */
589 while (next!=trail) {
590 store1(&store, next);
593 store1(&store, next);
602 if (cmpnumfld(revno,next->num,field+1)<0) {
604 rcserror("revision number %s too low",
605 partialno(&t,revno,field+1)
611 store1(&store, next);
614 } while (next && cmpnumfld(revno,next->num,field+1)>=0);
616 if ((length>field+1) && /*need exact hit */
617 (cmpnumfld(revno,trail->num,field+1) !=0)){
618 absent(revno, field+1);
621 if (length == field+1) {
622 if (date && cmpdate(date,trail->date)<0) {
623 rcserror("Revision %s has date %s.",
625 date2str(trail->date, datebuf)
629 if (author && strcmp(author,trail->author)!=0) {
630 rcserror("Revision %s has author %s.",
631 trail->num, trail->author
635 if (state && strcmp(state,trail->state)!=0) {
636 rcserror("Revision %s has state %s.",
638 trail->state ? trail->state : "<empty>"
643 bhead = trail->branches;
645 } while ((field+=2) <= length);
654 /* Function: looks up id in the list of symbolic names starting
655 * with pointer SYMBOLS, and returns a pointer to the corresponding
656 * revision number. Return 0 if not present.
659 register struct assoc const *next;
660 for (next = Symbols; next; next = next->nextassoc)
661 if (strcmp(id, next->symbol)==0)
666 int expandsym(source, target)
669 /* Function: Source points to a revision number. Expandsym copies
670 * the number to target, but replaces all symbolic fields in the
671 * source number with their numeric values.
672 * Expand a branch followed by `.' to the latest revision on that branch.
673 * Ignore `.' after a revision. Remove leading zeros.
674 * returns false on error;
677 return fexpandsym(source, target, (RILE*)0);
681 fexpandsym(source, target, fp)
685 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */
687 register char const *sp, *bp;
695 if (!sp || !*sp) { /* Accept 0 pointer as a legal value. */
699 if (sp[0] == KDELIM && !sp[1]) {
702 if (!*prevrev.string) {
703 workerror("working file lacks revision number");
706 bufscpy(target, prevrev.string);
709 tlim = tp + target->size;
713 register char *p = tp;
714 size_t s = tp - target->string;
717 switch (ctab[(unsigned char)*sp]) {
725 p = bufenlarge(target, &tlim);
735 p = bufenlarge(target, &tlim);
737 tp = target->string + s;
742 rcserror("Symbolic name `%s' is undefined.",tp);
746 /* skip leading zeros */
747 for (bp = tp; *bp=='0' && isdigit(bp[1]); bp++)
754 /* Insert default branch before initial `.'. */
762 getbranchno(b, target);
763 bp = tp = target->string;
764 tlim = tp + target->size;
769 while ((*tp++ = *bp++))
771 tp = bufenlarge(target, &tlim);
781 if (!(bp = branchtip(target->string)))
793 rcserror("improper revision number: %s", source);
798 namedrev(name, delta)
800 struct hshentry *delta;
801 /* Yield NAME if it names DELTA, 0 otherwise. */
804 char const *id = 0, *p, *val;
805 for (p = name; ; p++)
806 switch (ctab[(unsigned char)*p]) {
818 (val = lookupsym(id)) &&
819 strcmp(val, delta->num) == 0
835 struct hshentries *hs;
837 h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
838 return h ? h->num : (char const*)0;
844 return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
852 * Test the routines that generate a sequence of delta numbers
853 * needed to regenerate a given delta.
856 char const cmdid[] = "revtest";
860 int argc; char * argv[];
862 static struct buf numricrevno;
863 char symrevno[100]; /* used for input of revision numbers */
867 struct hshentries *gendeltas;
868 struct hshentry * target;
872 aputs("No input file\n",stderr);
873 exitmain(EXIT_FAILURE);
875 if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
876 faterror("can't open input file %s", argv[1]);
886 /* all output goes to stderr, to have diagnostics and */
887 /* errors in sequence. */
888 aputs("\nEnter revision number or <return> or '.': ",stderr);
889 if (!fgets(symrevno, 100, stdin)) break;
890 if (*symrevno == '.') break;
891 aprintf(stderr,"%s;\n",symrevno);
892 expandsym(symrevno,&numricrevno);
893 aprintf(stderr,"expanded number: %s; ",numricrevno.string);
894 aprintf(stderr,"Date: ");
895 fgets(date, 20, stdin); aprintf(stderr,"%s; ",date);
896 aprintf(stderr,"Author: ");
897 fgets(author, 20, stdin); aprintf(stderr,"%s; ",author);
898 aprintf(stderr,"State: ");
899 fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state);
900 target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
901 *state?state:(char*)0, &gendeltas);
904 aprintf(stderr,"%s\n",gendeltas->first->num);
905 gendeltas = gendeltas->next;
909 aprintf(stderr,"done\n");
910 exitmain(EXIT_SUCCESS);
913 void exiterr() { _exit(EXIT_FAILURE); }