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 * Revision 5.10 1995/06/16 06:19:24 eggert
34 * Revision 5.9 1995/06/01 16:23:43 eggert
35 * (cmpdate, normalizeyear): New functions work around MKS RCS incompatibility.
36 * (cmpnum, compartial): s[d] -> *(s+d) to work around Cray compiler bug.
37 * (genrevs, genbranch): cmpnum -> cmpdate
39 * Revision 5.8 1994/03/17 14:05:48 eggert
42 * Revision 5.7 1993/11/09 17:40:15 eggert
43 * Fix format string typos.
45 * Revision 5.6 1993/11/03 17:42:27 eggert
46 * Revision number `.N' now stands for `D.N', where D is the default branch.
47 * Add -z. Improve quality of diagnostics. Add `namedrev' for Name support.
49 * Revision 5.5 1992/07/28 16:12:44 eggert
50 * Identifiers may now start with a digit. Avoid `unsigned'.
52 * Revision 5.4 1992/01/06 02:42:34 eggert
53 * while (E) ; -> while (E) continue;
55 * Revision 5.3 1991/08/19 03:13:55 eggert
56 * Add `-r$', `-rB.'. Remove botches like `<now>' from messages. Tune.
58 * Revision 5.2 1991/04/21 11:58:28 eggert
61 * Revision 5.1 1991/02/25 07:12:43 eggert
62 * Avoid overflow when comparing revision numbers.
64 * Revision 5.0 1990/08/22 08:13:43 eggert
65 * Remove compile-time limits; use malloc instead.
66 * Ansify and Posixate. Tune.
67 * Remove possibility of an internal error. Remove lint.
69 * Revision 4.5 89/05/01 15:13:22 narten
70 * changed copyright header to reflect current distribution rules
72 * Revision 4.4 87/12/18 11:45:22 narten
73 * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
74 * since there's now a return value there with a value. (Guy Harris)
76 * Revision 4.3 87/10/18 10:38:42 narten
77 * Updating version numbers. Changes relative to version 1.1 actually
80 * Revision 1.3 87/09/24 14:00:37 narten
81 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
84 * Revision 1.2 87/03/27 14:22:37 jenkins
87 * Revision 4.1 83/03/25 21:10:45 wft
88 * Only changed $Header to $Id.
90 * Revision 3.4 82/12/04 13:24:08 wft
91 * Replaced getdelta() with gettree().
93 * Revision 3.3 82/11/28 21:33:15 wft
94 * fixed compartial() and compnum() for nil-parameters; fixed nils
95 * in error messages. Testprogram output shortenend.
97 * Revision 3.2 82/10/18 21:19:47 wft
98 * renamed compnum->cmpnum, compnumfld->cmpnumfld,
99 * numericrevno->numricrevno.
101 * Revision 3.1 82/10/11 19:46:09 wft
102 * changed expandsym() to check for source==nil; returns zero length string
108 libId(revId, "$FreeBSD$")
110 static char const *branchtip P((char const*));
111 static char const *lookupsym P((char const*));
112 static char const *normalizeyear P((char const*,char[5]));
113 static struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**));
114 static void absent P((char const*,int));
115 static void cantfindbranch P((char const*,char const[datesize],char const*,char const*));
116 static void store1 P((struct hshentries***,struct hshentry*));
123 /* Given a pointer s to a dotted number (date or revision number),
124 * countnumflds returns the number of digitfields in s.
127 register char const *sp;
133 if (*sp++ == '.') count++;
139 getbranchno(revno,branchno)
141 struct buf *branchno;
142 /* Given a revision number revno, getbranchno copies the number of the branch
143 * on which revno is into branchno. If revno itself is a branch number,
144 * it is copied unchanged.
147 register int numflds;
150 bufscpy(branchno, revno);
151 numflds=countnumflds(revno);
152 if (!(numflds & 1)) {
153 tp = branchno->string;
163 int cmpnum(num1, num2)
164 char const *num1, *num2;
165 /* compares the two dotted numbers num1 and num2 lexicographically
166 * by field. Individual fields are compared numerically.
167 * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
168 * omitted fields are assumed to be higher than the existing ones.
171 register char const *s1, *s2;
172 register size_t d1, d2;
175 s1 = num1 ? num1 : "";
176 s2 = num2 ? num2 : "";
179 /* Give precedence to shorter one. */
181 return (unsigned char)*s2;
185 /* Strip leading zeros, then find number of digits. */
186 while (*s1=='0') ++s1;
187 while (*s2=='0') ++s2;
188 for (d1=0; isdigit(*(s1+d1)); d1++) continue;
189 for (d2=0; isdigit(*(s2+d2)); d2++) continue;
191 /* Do not convert to integer; it might overflow! */
193 return d1<d2 ? -1 : 1;
194 if ((r = memcmp(s1, s2, d1)))
207 int cmpnumfld(num1, num2, fld)
208 char const *num1, *num2;
210 /* Compare the two dotted numbers at field fld.
211 * num1 and num2 must have at least fld fields.
212 * fld must be positive.
215 register char const *s1, *s2;
216 register size_t d1, d2;
220 /* skip fld-1 fields */
227 /* Now s1 and s2 point to the beginning of the respective fields */
228 while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
229 while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
231 return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
239 * Compare the two dates. This is just like cmpnum,
240 * except that for compatibility with old versions of RCS,
241 * 1900 is added to dates with two-digit years.
244 char year1[5], year2[5];
245 int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1);
250 while (isdigit(*d1)) d1++; d1 += *d1=='.';
251 while (isdigit(*d2)) d2++; d2 += *d2=='.';
252 return cmpnum(d1, d2);
257 normalizeyear(date, year)
261 if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) {
274 cantfindbranch(revno, date, author, state)
275 char const *revno, date[datesize], *author, *state;
277 char datebuf[datesize + zonelenmax];
279 rcserror("No revision on branch %s has%s%s%s%s%s%s.",
281 date ? " a date before " : "",
282 date ? date2str(date,datebuf) : "",
283 author ? " and author "+(date?0:4) : "",
284 author ? author : "",
285 state ? " and state "+(date||author?0:4) : "",
297 rcserror("%s %s absent", field&1?"revision":"branch",
298 partialno(&t,revno,field)
305 compartial(num1, num2, length)
306 char const *num1, *num2;
309 /* compare the first "length" fields of two dot numbers;
310 the omitted field is considered to be larger than any number */
311 /* restriction: at least one number has length or more fields */
314 register char const *s1, *s2;
315 register size_t d1, d2;
318 s1 = num1; s2 = num2;
326 while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
327 while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
330 return d1<d2 ? -1 : 1;
331 if ((r = memcmp(s1, s2, d1)))
339 if (*s1 == '.') s1++;
340 if (*s2 == '.') s2++;
345 char * partialno(rev1,rev2,length)
349 /* Function: Copies length fields of revision number rev2 into rev1.
350 * Return rev1's string.
358 while (*r1!='.' && *r1)
363 /* eliminate last '.'*/
373 struct hshentries ***store;
374 struct hshentry *next;
376 * Allocate a new list node that addresses NEXT.
377 * Append it to the list that **STORE is the end pointer of.
380 register struct hshentries *p;
382 p = ftalloc(struct hshentries);
388 struct hshentry * genrevs(revno,date,author,state,store)
389 char const *revno, *date, *author, *state;
390 struct hshentries **store;
391 /* Function: finds the deltas needed for reconstructing the
392 * revision given by revno, date, author, and state, and stores pointers
393 * to these deltas into a list whose starting address is given by store.
394 * The last delta (target delta) is returned.
395 * If the proper delta could not be found, 0 is returned.
399 register struct hshentry * next;
401 char const *branchnum;
403 char datebuf[datesize + zonelenmax];
407 if (!(next = Head)) {
408 rcserror("RCS file empty");
412 length = countnumflds(revno);
415 /* at least one field; find branch exactly */
416 while ((result=cmpnumfld(revno,next->num,1)) < 0) {
417 store1(&store, next);
420 rcserror("branch number %s too low", partialno(&t,revno,1));
431 /* pick latest one on given branch */
432 branchnum = next->num; /* works even for empty revno*/
434 cmpnumfld(branchnum,next->num,1) == 0 &&
436 (date && cmpdate(date,next->date) < 0) ||
437 (author && strcmp(author,next->author) != 0) ||
438 (state && strcmp(state,next->state) != 0)
442 store1(&store, next);
446 (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
448 length ? revno : partialno(&t,branchnum,1),
453 store1(&store, next);
460 /* find revision; may go low if length==2*/
461 while ((result=cmpnumfld(revno,next->num,2)) < 0 &&
462 (cmpnumfld(revno,next->num,1)==0) ) {
463 store1(&store, next);
469 if (!next || cmpnumfld(revno,next->num,1) != 0) {
470 rcserror("revision number %s too low", partialno(&t,revno,2));
473 if ((length>2) && (result!=0)) {
479 store1(&store, next);
482 return genbranch(next,revno,length,date,author,state,store);
483 else { /* length == 2*/
484 if (date && cmpdate(date,next->date)<0) {
485 rcserror("Revision %s has date %s.",
487 date2str(next->date, datebuf)
491 if (author && strcmp(author,next->author)!=0) {
492 rcserror("Revision %s has author %s.",
493 next->num, next->author
497 if (state && strcmp(state,next->state)!=0) {
498 rcserror("Revision %s has state %s.",
500 next->state ? next->state : "<empty>"
516 static struct hshentry *
517 genbranch(bpoint, revno, length, date, author, state, store)
518 struct hshentry const *bpoint;
521 char const *date, *author, *state;
522 struct hshentries **store;
523 /* Function: given a branchpoint, a revision number, date, author, and state,
524 * genbranch finds the deltas necessary to reconstruct the given revision
525 * from the branch point on.
526 * Pointers to the found deltas are stored in a list beginning with store.
527 * revno must be on a side branch.
532 register struct hshentry * next, * trail;
533 register struct branchhead const *bhead;
536 char datebuf[datesize + zonelenmax];
539 bhead = bpoint->branches;
544 rcserror("no side branches present for %s",
545 partialno(&t,revno,field-1)
552 /*branches are arranged in increasing order*/
553 while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
554 bhead = bhead->nextbranch;
557 rcserror("branch number %s too high",
558 partialno(&t,revno,field)
566 absent(revno, field);
572 /* pick latest one on that branch */
574 do { if ((!date || cmpdate(date,next->date)>=0) &&
575 (!author || strcmp(author,next->author)==0) &&
576 (!state || strcmp(state,next->state)==0)
582 cantfindbranch(revno, date, author, state);
584 } else { /* print up to last one suitable */
586 while (next!=trail) {
587 store1(&store, next);
590 store1(&store, next);
599 if (cmpnumfld(revno,next->num,field+1)<0) {
601 rcserror("revision number %s too low",
602 partialno(&t,revno,field+1)
608 store1(&store, next);
611 } while (next && cmpnumfld(revno,next->num,field+1)>=0);
613 if ((length>field+1) && /*need exact hit */
614 (cmpnumfld(revno,trail->num,field+1) !=0)){
615 absent(revno, field+1);
618 if (length == field+1) {
619 if (date && cmpdate(date,trail->date)<0) {
620 rcserror("Revision %s has date %s.",
622 date2str(trail->date, datebuf)
626 if (author && strcmp(author,trail->author)!=0) {
627 rcserror("Revision %s has author %s.",
628 trail->num, trail->author
632 if (state && strcmp(state,trail->state)!=0) {
633 rcserror("Revision %s has state %s.",
635 trail->state ? trail->state : "<empty>"
640 bhead = trail->branches;
642 } while ((field+=2) <= length);
651 /* Function: looks up id in the list of symbolic names starting
652 * with pointer SYMBOLS, and returns a pointer to the corresponding
653 * revision number. Return 0 if not present.
656 register struct assoc const *next;
657 for (next = Symbols; next; next = next->nextassoc)
658 if (strcmp(id, next->symbol)==0)
663 int expandsym(source, target)
666 /* Function: Source points to a revision number. Expandsym copies
667 * the number to target, but replaces all symbolic fields in the
668 * source number with their numeric values.
669 * Expand a branch followed by `.' to the latest revision on that branch.
670 * Ignore `.' after a revision. Remove leading zeros.
671 * returns false on error;
674 return fexpandsym(source, target, (RILE*)0);
678 fexpandsym(source, target, fp)
682 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */
684 register char const *sp, *bp;
692 if (!sp || !*sp) { /* Accept 0 pointer as a legal value. */
696 if (sp[0] == KDELIM && !sp[1]) {
699 if (!*prevrev.string) {
700 workerror("working file lacks revision number");
703 bufscpy(target, prevrev.string);
706 tlim = tp + target->size;
710 register char *p = tp;
711 size_t s = tp - target->string;
714 switch (ctab[(unsigned char)*sp]) {
722 p = bufenlarge(target, &tlim);
732 p = bufenlarge(target, &tlim);
734 tp = target->string + s;
739 rcserror("Symbolic name `%s' is undefined.",tp);
743 /* skip leading zeros */
744 for (bp = tp; *bp=='0' && isdigit(bp[1]); bp++)
751 /* Insert default branch before initial `.'. */
759 getbranchno(b, target);
760 bp = tp = target->string;
761 tlim = tp + target->size;
765 while ((*tp++ = *bp++))
767 tp = bufenlarge(target, &tlim);
777 if (!(bp = branchtip(target->string)))
789 rcserror("improper revision number: %s", source);
794 namedrev(name, delta)
796 struct hshentry *delta;
797 /* Yield NAME if it names DELTA, 0 otherwise. */
800 char const *id = 0, *p, *val;
801 for (p = name; ; p++)
802 switch (ctab[(unsigned char)*p]) {
814 (val = lookupsym(id)) &&
815 strcmp(val, delta->num) == 0
831 struct hshentries *hs;
833 h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
834 return h ? h->num : (char const*)0;
840 return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
848 * Test the routines that generate a sequence of delta numbers
849 * needed to regenerate a given delta.
852 char const cmdid[] = "revtest";
856 int argc; char * argv[];
858 static struct buf numricrevno;
859 char symrevno[100]; /* used for input of revision numbers */
863 struct hshentries *gendeltas;
864 struct hshentry * target;
868 aputs("No input file\n",stderr);
869 exitmain(EXIT_FAILURE);
871 if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
872 faterror("can't open input file %s", argv[1]);
882 /* all output goes to stderr, to have diagnostics and */
883 /* errors in sequence. */
884 aputs("\nEnter revision number or <return> or '.': ",stderr);
885 if (!fgets(symrevno, 100, stdin)) break;
886 if (*symrevno == '.') break;
887 aprintf(stderr,"%s;\n",symrevno);
888 expandsym(symrevno,&numricrevno);
889 aprintf(stderr,"expanded number: %s; ",numricrevno.string);
890 aprintf(stderr,"Date: ");
891 fgets(date, 20, stdin); aprintf(stderr,"%s; ",date);
892 aprintf(stderr,"Author: ");
893 fgets(author, 20, stdin); aprintf(stderr,"%s; ",author);
894 aprintf(stderr,"State: ");
895 fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state);
896 target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
897 *state?state:(char*)0, &gendeltas);
900 aprintf(stderr,"%s\n",gendeltas->first->num);
901 gendeltas = gendeltas->next;
905 aprintf(stderr,"done\n");
906 exitmain(EXIT_SUCCESS);
909 void exiterr() { _exit(EXIT_FAILURE); }