1 /* $NetBSD: rcsrev.c,v 1.5 1996/10/15 07:00:24 veego Exp $ */
3 /* Handle RCS revision numbers. */
5 /* Copyright 1982, 1988, 1989 Walter Tichy
6 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7 Distributed under license by the Free Software Foundation, Inc.
9 This file is part of RCS.
11 RCS is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2, or (at your option)
16 RCS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with RCS; see the file COPYING.
23 If not, write to the Free Software Foundation,
24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 Report problems and direct all questions to:
28 rcs-bugs@cs.purdue.edu
34 * Revision 1.5 1996/10/15 07:00:24 veego
37 * Revision 5.10 1995/06/16 06:19:24 eggert
40 * Revision 5.9 1995/06/01 16:23:43 eggert
41 * (cmpdate, normalizeyear): New functions work around MKS RCS incompatibility.
42 * (cmpnum, compartial): s[d] -> *(s+d) to work around Cray compiler bug.
43 * (genrevs, genbranch): cmpnum -> cmpdate
45 * Revision 5.8 1994/03/17 14:05:48 eggert
48 * Revision 5.7 1993/11/09 17:40:15 eggert
49 * Fix format string typos.
51 * Revision 5.6 1993/11/03 17:42:27 eggert
52 * Revision number `.N' now stands for `D.N', where D is the default branch.
53 * Add -z. Improve quality of diagnostics. Add `namedrev' for Name support.
55 * Revision 5.5 1992/07/28 16:12:44 eggert
56 * Identifiers may now start with a digit. Avoid `unsigned'.
58 * Revision 5.4 1992/01/06 02:42:34 eggert
59 * while (E) ; -> while (E) continue;
61 * Revision 5.3 1991/08/19 03:13:55 eggert
62 * Add `-r$', `-rB.'. Remove botches like `<now>' from messages. Tune.
64 * Revision 5.2 1991/04/21 11:58:28 eggert
67 * Revision 5.1 1991/02/25 07:12:43 eggert
68 * Avoid overflow when comparing revision numbers.
70 * Revision 5.0 1990/08/22 08:13:43 eggert
71 * Remove compile-time limits; use malloc instead.
72 * Ansify and Posixate. Tune.
73 * Remove possibility of an internal error. Remove lint.
75 * Revision 4.5 89/05/01 15:13:22 narten
76 * changed copyright header to reflect current distribution rules
78 * Revision 4.4 87/12/18 11:45:22 narten
79 * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
80 * since there's now a return value there with a value. (Guy Harris)
82 * Revision 4.3 87/10/18 10:38:42 narten
83 * Updating version numbers. Changes relative to version 1.1 actually
86 * Revision 1.3 87/09/24 14:00:37 narten
87 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
90 * Revision 1.2 87/03/27 14:22:37 jenkins
93 * Revision 4.1 83/03/25 21:10:45 wft
94 * Only changed $Header to $Id.
96 * Revision 3.4 82/12/04 13:24:08 wft
97 * Replaced getdelta() with gettree().
99 * Revision 3.3 82/11/28 21:33:15 wft
100 * fixed compartial() and compnum() for nil-parameters; fixed nils
101 * in error messages. Testprogram output shortenend.
103 * Revision 3.2 82/10/18 21:19:47 wft
104 * renamed compnum->cmpnum, compnumfld->cmpnumfld,
105 * numericrevno->numricrevno.
107 * Revision 3.1 82/10/11 19:46:09 wft
108 * changed expandsym() to check for source==nil; returns zero length string
114 libId(revId
, "Id: rcsrev.c,v 5.10 1995/06/16 06:19:24 eggert Exp")
116 static char const *branchtip
P((char const*));
117 static char const *lookupsym
P((char const*));
118 static char const *normalizeyear
P((char const*,char[5]));
119 static struct hshentry
*genbranch
P((struct hshentry
const*,char const*,int,char const*,char const*,char const*,struct hshentries
**));
120 static void absent
P((char const*,int));
121 static void cantfindbranch
P((char const*,char const[datesize
],char const*,char const*));
122 static void store1
P((struct hshentries
***,struct hshentry
*));
129 /* Given a pointer s to a dotted number (date or revision number),
130 * countnumflds returns the number of digitfields in s.
133 register char const *sp
;
139 if (*sp
++ == '.') count
++;
145 getbranchno(revno
,branchno
)
147 struct buf
*branchno
;
148 /* Given a revision number revno, getbranchno copies the number of the branch
149 * on which revno is into branchno. If revno itself is a branch number,
150 * it is copied unchanged.
153 register int numflds
;
156 bufscpy(branchno
, revno
);
157 numflds
=countnumflds(revno
);
158 if (!(numflds
& 1)) {
159 tp
= branchno
->string
;
169 int cmpnum(num1
, num2
)
170 char const *num1
, *num2
;
171 /* compares the two dotted numbers num1 and num2 lexicographically
172 * by field. Individual fields are compared numerically.
173 * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
174 * omitted fields are assumed to be higher than the existing ones.
177 register char const *s1
, *s2
;
178 register size_t d1
, d2
;
181 s1
= num1
? num1
: "";
182 s2
= num2
? num2
: "";
185 /* Give precedence to shorter one. */
187 return (unsigned char)*s2
;
191 /* Strip leading zeros, then find number of digits. */
192 while (*s1
=='0') ++s1
;
193 while (*s2
=='0') ++s2
;
194 for (d1
=0; isdigit(*(s1
+d1
)); d1
++) continue;
195 for (d2
=0; isdigit(*(s2
+d2
)); d2
++) continue;
197 /* Do not convert to integer; it might overflow! */
199 return d1
<d2
? -1 : 1;
200 if ((r
= memcmp(s1
, s2
, d1
)))
213 int cmpnumfld(num1
, num2
, fld
)
214 char const *num1
, *num2
;
216 /* Compare the two dotted numbers at field fld.
217 * num1 and num2 must have at least fld fields.
218 * fld must be positive.
221 register char const *s1
, *s2
;
222 register size_t d1
, d2
;
226 /* skip fld-1 fields */
233 /* Now s1 and s2 point to the beginning of the respective fields */
234 while (*s1
=='0') ++s1
; for (d1
=0; isdigit(*(s1
+d1
)); d1
++) continue;
235 while (*s2
=='0') ++s2
; for (d2
=0; isdigit(*(s2
+d2
)); d2
++) continue;
237 return d1
<d2
? -1 : d1
==d2
? memcmp(s1
,s2
,d1
) : 1;
245 * Compare the two dates. This is just like cmpnum,
246 * except that for compatibility with old versions of RCS,
247 * 1900 is added to dates with two-digit years.
250 char year1
[5], year2
[5];
251 int r
= cmpnumfld(normalizeyear(d1
,year1
), normalizeyear(d2
,year2
), 1);
256 while (isdigit(*d1
)) d1
++; d1
+= *d1
=='.';
257 while (isdigit(*d2
)) d2
++; d2
+= *d2
=='.';
258 return cmpnum(d1
, d2
);
263 normalizeyear(date
, year
)
267 if (isdigit(date
[0]) && isdigit(date
[1]) && !isdigit(date
[2])) {
280 cantfindbranch(revno
, date
, author
, state
)
281 char const *revno
, date
[datesize
], *author
, *state
;
283 char datebuf
[datesize
+ zonelenmax
];
285 rcserror("No revision on branch %s has%s%s%s%s%s%s.",
287 date
? " a date before " : "",
288 date
? date2str(date
,datebuf
) : "",
289 author
? " and author "+(date
?0:4) : "",
290 author
? author
: "",
291 state
? " and state "+(date
||author
?0:4) : "",
303 rcserror("%s %s absent", field
&1?"revision":"branch",
304 partialno(&t
,revno
,field
)
311 compartial(num1
, num2
, length
)
312 char const *num1
, *num2
;
315 /* compare the first "length" fields of two dot numbers;
316 the omitted field is considered to be larger than any number */
317 /* restriction: at least one number has length or more fields */
320 register char const *s1
, *s2
;
321 register size_t d1
, d2
;
324 s1
= num1
; s2
= num2
;
332 while (*s1
=='0') ++s1
; for (d1
=0; isdigit(*(s1
+d1
)); d1
++) continue;
333 while (*s2
=='0') ++s2
; for (d2
=0; isdigit(*(s2
+d2
)); d2
++) continue;
336 return d1
<d2
? -1 : 1;
337 if ((r
= memcmp(s1
, s2
, d1
)))
345 if (*s1
== '.') s1
++;
346 if (*s2
== '.') s2
++;
351 char * partialno(rev1
,rev2
,length
)
355 /* Function: Copies length fields of revision number rev2 into rev1.
356 * Return rev1's string.
364 while (*r1
!='.' && *r1
)
369 /* eliminate last '.'*/
379 struct hshentries
***store
;
380 struct hshentry
*next
;
382 * Allocate a new list node that addresses NEXT.
383 * Append it to the list that **STORE is the end pointer of.
386 register struct hshentries
*p
;
388 p
= ftalloc(struct hshentries
);
394 struct hshentry
* genrevs(revno
,date
,author
,state
,store
)
395 char const *revno
, *date
, *author
, *state
;
396 struct hshentries
**store
;
397 /* Function: finds the deltas needed for reconstructing the
398 * revision given by revno, date, author, and state, and stores pointers
399 * to these deltas into a list whose starting address is given by store.
400 * The last delta (target delta) is returned.
401 * If the proper delta could not be found, 0 is returned.
405 register struct hshentry
* next
;
407 char const *branchnum
;
409 char datebuf
[datesize
+ zonelenmax
];
413 if (!(next
= Head
)) {
414 rcserror("RCS file empty");
418 length
= countnumflds(revno
);
421 /* at least one field; find branch exactly */
422 while ((result
=cmpnumfld(revno
,next
->num
,1)) < 0) {
423 store1(&store
, next
);
426 rcserror("branch number %s too low", partialno(&t
,revno
,1));
437 /* pick latest one on given branch */
438 branchnum
= next
->num
; /* works even for empty revno*/
440 cmpnumfld(branchnum
,next
->num
,1) == 0 &&
442 (date
&& cmpdate(date
,next
->date
) < 0) ||
443 (author
&& strcmp(author
,next
->author
) != 0) ||
444 (state
&& strcmp(state
,next
->state
) != 0)
448 store1(&store
, next
);
452 (cmpnumfld(branchnum
,next
->num
,1)!=0))/*overshot*/ {
454 length
? revno
: partialno(&t
,branchnum
,1),
459 store1(&store
, next
);
466 /* find revision; may go low if length==2*/
467 while ((result
=cmpnumfld(revno
,next
->num
,2)) < 0 &&
468 (cmpnumfld(revno
,next
->num
,1)==0) ) {
469 store1(&store
, next
);
475 if (!next
|| cmpnumfld(revno
,next
->num
,1) != 0) {
476 rcserror("revision number %s too low", partialno(&t
,revno
,2));
479 if ((length
>2) && (result
!=0)) {
485 store1(&store
, next
);
488 return genbranch(next
,revno
,length
,date
,author
,state
,store
);
489 else { /* length == 2*/
490 if (date
&& cmpdate(date
,next
->date
)<0) {
491 rcserror("Revision %s has date %s.",
493 date2str(next
->date
, datebuf
)
497 if (author
&& strcmp(author
,next
->author
)!=0) {
498 rcserror("Revision %s has author %s.",
499 next
->num
, next
->author
503 if (state
&& strcmp(state
,next
->state
)!=0) {
504 rcserror("Revision %s has state %s.",
506 next
->state
? next
->state
: "<empty>"
522 static struct hshentry
*
523 genbranch(bpoint
, revno
, length
, date
, author
, state
, store
)
524 struct hshentry
const *bpoint
;
527 char const *date
, *author
, *state
;
528 struct hshentries
**store
;
529 /* Function: given a branchpoint, a revision number, date, author, and state,
530 * genbranch finds the deltas necessary to reconstruct the given revision
531 * from the branch point on.
532 * Pointers to the found deltas are stored in a list beginning with store.
533 * revno must be on a side branch.
538 register struct hshentry
* next
, * trail
;
539 register struct branchhead
const *bhead
;
542 char datebuf
[datesize
+ zonelenmax
];
545 bhead
= bpoint
->branches
;
550 rcserror("no side branches present for %s",
551 partialno(&t
,revno
,field
-1)
558 /*branches are arranged in increasing order*/
559 while (0 < (result
=cmpnumfld(revno
,bhead
->hsh
->num
,field
))) {
560 bhead
= bhead
->nextbranch
;
563 rcserror("branch number %s too high",
564 partialno(&t
,revno
,field
)
572 absent(revno
, field
);
578 /* pick latest one on that branch */
580 do { if ((!date
|| cmpdate(date
,next
->date
)>=0) &&
581 (!author
|| strcmp(author
,next
->author
)==0) &&
582 (!state
|| strcmp(state
,next
->state
)==0)
588 cantfindbranch(revno
, date
, author
, state
);
590 } else { /* print up to last one suitable */
592 while (next
!=trail
) {
593 store1(&store
, next
);
596 store1(&store
, next
);
605 if (cmpnumfld(revno
,next
->num
,field
+1)<0) {
607 rcserror("revision number %s too low",
608 partialno(&t
,revno
,field
+1)
614 store1(&store
, next
);
617 } while (next
&& cmpnumfld(revno
,next
->num
,field
+1)>=0);
619 if ((length
>field
+1) && /*need exact hit */
620 (cmpnumfld(revno
,trail
->num
,field
+1) !=0)){
621 absent(revno
, field
+1);
624 if (length
== field
+1) {
625 if (date
&& cmpdate(date
,trail
->date
)<0) {
626 rcserror("Revision %s has date %s.",
628 date2str(trail
->date
, datebuf
)
632 if (author
&& strcmp(author
,trail
->author
)!=0) {
633 rcserror("Revision %s has author %s.",
634 trail
->num
, trail
->author
641 if (trail
->state
== NULL
)
643 else if (strcmp(trail
->state
, state
) != 0)
649 rcserror("Revision %s has state %s.",
654 bhead
= trail
->branches
;
656 } while ((field
+=2) <= length
);
665 /* Function: looks up id in the list of symbolic names starting
666 * with pointer SYMBOLS, and returns a pointer to the corresponding
667 * revision number. Return 0 if not present.
670 register struct assoc
const *next
;
671 for (next
= Symbols
; next
; next
= next
->nextassoc
)
672 if (strcmp(id
, next
->symbol
)==0)
677 int expandsym(source
, target
)
680 /* Function: Source points to a revision number. Expandsym copies
681 * the number to target, but replaces all symbolic fields in the
682 * source number with their numeric values.
683 * Expand a branch followed by `.' to the latest revision on that branch.
684 * Ignore `.' after a revision. Remove leading zeros.
685 * returns false on error;
688 return fexpandsym(source
, target
, (RILE
*)0);
692 fexpandsym(source
, target
, fp
)
696 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */
698 register char const *sp
, *bp
;
706 if (!sp
|| !*sp
) { /* Accept 0 pointer as a legal value. */
710 if (sp
[0] == KDELIM
&& !sp
[1]) {
713 if (!*prevrev
.string
) {
714 workerror("working file lacks revision number");
717 bufscpy(target
, prevrev
.string
);
720 tlim
= tp
+ target
->size
;
724 register char *p
= tp
;
725 size_t s
= tp
- target
->string
;
728 switch (ctab
[(unsigned char)*sp
]) {
736 p
= bufenlarge(target
, &tlim
);
746 p
= bufenlarge(target
, &tlim
);
748 tp
= target
->string
+ s
;
753 rcserror("Symbolic name `%s' is undefined.",tp
);
757 /* skip leading zeros */
758 for (bp
= tp
; *bp
=='0' && isdigit(bp
[1]); bp
++)
765 /* Insert default branch before initial `.'. */
773 getbranchno(b
, target
);
774 bp
= tp
= target
->string
;
775 tlim
= tp
+ target
->size
;
779 while ((*tp
++ = *bp
++))
781 tp
= bufenlarge(target
, &tlim
);
791 if (!(bp
= branchtip(target
->string
)))
803 rcserror("improper revision number: %s", source
);
808 namedrev(name
, delta
)
810 struct hshentry
*delta
;
811 /* Yield NAME if it names DELTA, 0 otherwise. */
814 char const *id
= 0, *p
, *val
;
815 for (p
= name
; ; p
++)
816 switch (ctab
[(unsigned char)*p
]) {
828 (val
= lookupsym(id
)) &&
829 strcmp(val
, delta
->num
) == 0
845 struct hshentries
*hs
;
847 h
= genrevs(branch
, (char*)0, (char*)0, (char*)0, &hs
);
848 return h
? h
->num
: (char const*)0;
854 return Dbranch
? branchtip(Dbranch
) : Head
? Head
->num
: (char const*)0;
862 * Test the routines that generate a sequence of delta numbers
863 * needed to regenerate a given delta.
866 char const cmdid
[] = "revtest";
870 int argc
; char * argv
[];
872 static struct buf numricrevno
;
873 char symrevno
[100]; /* used for input of revision numbers */
877 struct hshentries
*gendeltas
;
878 struct hshentry
* target
;
882 aputs("No input file\n",stderr
);
883 exitmain(EXIT_FAILURE
);
885 if (!(finptr
=Iopen(argv
[1], FOPEN_R
, (struct stat
*)0))) {
886 faterror("can't open input file %s", argv
[1]);
896 /* all output goes to stderr, to have diagnostics and */
897 /* errors in sequence. */
898 aputs("\nEnter revision number or <return> or '.': ",stderr
);
899 if (!fgets(symrevno
, 100, stdin
)) break;
900 if (*symrevno
== '.') break;
901 aprintf(stderr
,"%s;\n",symrevno
);
902 expandsym(symrevno
,&numricrevno
);
903 aprintf(stderr
,"expanded number: %s; ",numricrevno
.string
);
904 aprintf(stderr
,"Date: ");
905 fgets(date
, 20, stdin
); aprintf(stderr
,"%s; ",date
);
906 aprintf(stderr
,"Author: ");
907 fgets(author
, 20, stdin
); aprintf(stderr
,"%s; ",author
);
908 aprintf(stderr
,"State: ");
909 fgets(state
, 20, stdin
); aprintf(stderr
, "%s;\n", state
);
910 target
= genrevs(numricrevno
.string
, *date
?date
:(char *)0, *author
?author
:(char *)0,
911 *state
?state
:(char*)0, &gendeltas
);
914 aprintf(stderr
,"%s\n",gendeltas
->first
->num
);
915 gendeltas
= gendeltas
->next
;
919 aprintf(stderr
,"done\n");
920 exitmain(EXIT_SUCCESS
);
923 void exiterr() { _exit(EXIT_FAILURE
); }