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 */
233 for (d1
=0; isdigit(*(s1
+d1
)); d1
++)
237 for (d2
=0; isdigit(*(s2
+d2
)); d2
++)
240 return d1
<d2
? -1 : d1
==d2
? memcmp(s1
,s2
,d1
) : 1;
248 * Compare the two dates. This is just like cmpnum,
249 * except that for compatibility with old versions of RCS,
250 * 1900 is added to dates with two-digit years.
253 char year1
[5], year2
[5];
254 int r
= cmpnumfld(normalizeyear(d1
,year1
), normalizeyear(d2
,year2
), 1);
265 return cmpnum(d1
, d2
);
270 normalizeyear(date
, year
)
274 if (isdigit(date
[0]) && isdigit(date
[1]) && !isdigit(date
[2])) {
287 cantfindbranch(revno
, date
, author
, state
)
288 char const *revno
, date
[datesize
], *author
, *state
;
290 char datebuf
[datesize
+ zonelenmax
];
292 rcserror("No revision on branch %s has%s%s%s%s%s%s.",
294 date
? " a date before " : "",
295 date
? date2str(date
,datebuf
) : "",
296 author
? " and author "+(date
?0:4) : "",
297 author
? author
: "",
298 state
? " and state "+(date
||author
?0:4) : "",
310 rcserror("%s %s absent", field
&1?"revision":"branch",
311 partialno(&t
,revno
,field
)
318 compartial(num1
, num2
, length
)
319 char const *num1
, *num2
;
322 /* compare the first "length" fields of two dot numbers;
323 the omitted field is considered to be larger than any number */
324 /* restriction: at least one number has length or more fields */
327 register char const *s1
, *s2
;
328 register size_t d1
, d2
;
331 s1
= num1
; s2
= num2
;
341 for (d1
=0; isdigit(*(s1
+d1
)); d1
++)
345 for (d2
=0; isdigit(*(s2
+d2
)); d2
++)
349 return d1
<d2
? -1 : 1;
350 if ((r
= memcmp(s1
, s2
, d1
)))
358 if (*s1
== '.') s1
++;
359 if (*s2
== '.') s2
++;
364 char * partialno(rev1
,rev2
,length
)
368 /* Function: Copies length fields of revision number rev2 into rev1.
369 * Return rev1's string.
377 while (*r1
!='.' && *r1
)
382 /* eliminate last '.'*/
392 struct hshentries
***store
;
393 struct hshentry
*next
;
395 * Allocate a new list node that addresses NEXT.
396 * Append it to the list that **STORE is the end pointer of.
399 register struct hshentries
*p
;
401 p
= ftalloc(struct hshentries
);
407 struct hshentry
* genrevs(revno
,date
,author
,state
,store
)
408 char const *revno
, *date
, *author
, *state
;
409 struct hshentries
**store
;
410 /* Function: finds the deltas needed for reconstructing the
411 * revision given by revno, date, author, and state, and stores pointers
412 * to these deltas into a list whose starting address is given by store.
413 * The last delta (target delta) is returned.
414 * If the proper delta could not be found, 0 is returned.
418 register struct hshentry
* next
;
420 char const *branchnum
;
422 char datebuf
[datesize
+ zonelenmax
];
426 if (!(next
= Head
)) {
427 rcserror("RCS file empty");
431 length
= countnumflds(revno
);
434 /* at least one field; find branch exactly */
435 while ((result
=cmpnumfld(revno
,next
->num
,1)) < 0) {
436 store1(&store
, next
);
439 rcserror("branch number %s too low", partialno(&t
,revno
,1));
450 /* pick latest one on given branch */
451 branchnum
= next
->num
; /* works even for empty revno*/
453 cmpnumfld(branchnum
,next
->num
,1) == 0 &&
455 (date
&& cmpdate(date
,next
->date
) < 0) ||
456 (author
&& strcmp(author
,next
->author
) != 0) ||
457 (state
&& strcmp(state
,next
->state
) != 0)
461 store1(&store
, next
);
465 (cmpnumfld(branchnum
,next
->num
,1)!=0))/*overshot*/ {
467 length
? revno
: partialno(&t
,branchnum
,1),
472 store1(&store
, next
);
479 /* find revision; may go low if length==2*/
480 while ((result
=cmpnumfld(revno
,next
->num
,2)) < 0 &&
481 (cmpnumfld(revno
,next
->num
,1)==0) ) {
482 store1(&store
, next
);
488 if (!next
|| cmpnumfld(revno
,next
->num
,1) != 0) {
489 rcserror("revision number %s too low", partialno(&t
,revno
,2));
492 if ((length
>2) && (result
!=0)) {
498 store1(&store
, next
);
501 return genbranch(next
,revno
,length
,date
,author
,state
,store
);
502 else { /* length == 2*/
503 if (date
&& cmpdate(date
,next
->date
)<0) {
504 rcserror("Revision %s has date %s.",
506 date2str(next
->date
, datebuf
)
510 if (author
&& strcmp(author
,next
->author
)!=0) {
511 rcserror("Revision %s has author %s.",
512 next
->num
, next
->author
516 if (state
&& strcmp(state
,next
->state
)!=0) {
517 rcserror("Revision %s has state %s.",
519 next
->state
? next
->state
: "<empty>"
535 static struct hshentry
*
536 genbranch(bpoint
, revno
, length
, date
, author
, state
, store
)
537 struct hshentry
const *bpoint
;
540 char const *date
, *author
, *state
;
541 struct hshentries
**store
;
542 /* Function: given a branchpoint, a revision number, date, author, and state,
543 * genbranch finds the deltas necessary to reconstruct the given revision
544 * from the branch point on.
545 * Pointers to the found deltas are stored in a list beginning with store.
546 * revno must be on a side branch.
551 register struct hshentry
* next
, * trail
;
552 register struct branchhead
const *bhead
;
555 char datebuf
[datesize
+ zonelenmax
];
558 bhead
= bpoint
->branches
;
563 rcserror("no side branches present for %s",
564 partialno(&t
,revno
,field
-1)
571 /*branches are arranged in increasing order*/
572 while (0 < (result
=cmpnumfld(revno
,bhead
->hsh
->num
,field
))) {
573 bhead
= bhead
->nextbranch
;
576 rcserror("branch number %s too high",
577 partialno(&t
,revno
,field
)
585 absent(revno
, field
);
591 /* pick latest one on that branch */
593 do { if ((!date
|| cmpdate(date
,next
->date
)>=0) &&
594 (!author
|| strcmp(author
,next
->author
)==0) &&
595 (!state
|| strcmp(state
,next
->state
)==0)
601 cantfindbranch(revno
, date
, author
, state
);
603 } else { /* print up to last one suitable */
605 while (next
!=trail
) {
606 store1(&store
, next
);
609 store1(&store
, next
);
618 if (cmpnumfld(revno
,next
->num
,field
+1)<0) {
620 rcserror("revision number %s too low",
621 partialno(&t
,revno
,field
+1)
627 store1(&store
, next
);
630 } while (next
&& cmpnumfld(revno
,next
->num
,field
+1)>=0);
632 if ((length
>field
+1) && /*need exact hit */
633 (cmpnumfld(revno
,trail
->num
,field
+1) !=0)){
634 absent(revno
, field
+1);
637 if (length
== field
+1) {
638 if (date
&& cmpdate(date
,trail
->date
)<0) {
639 rcserror("Revision %s has date %s.",
641 date2str(trail
->date
, datebuf
)
645 if (author
&& strcmp(author
,trail
->author
)!=0) {
646 rcserror("Revision %s has author %s.",
647 trail
->num
, trail
->author
651 if (state
&& strcmp(state
,trail
->state
)!=0) {
652 rcserror("Revision %s has state %s.",
654 trail
->state
? trail
->state
: "<empty>"
659 bhead
= trail
->branches
;
661 } while ((field
+=2) <= length
);
670 /* Function: looks up id in the list of symbolic names starting
671 * with pointer SYMBOLS, and returns a pointer to the corresponding
672 * revision number. Return 0 if not present.
675 register struct assoc
const *next
;
676 for (next
= Symbols
; next
; next
= next
->nextassoc
)
677 if (strcmp(id
, next
->symbol
)==0)
682 int expandsym(source
, target
)
685 /* Function: Source points to a revision number. Expandsym copies
686 * the number to target, but replaces all symbolic fields in the
687 * source number with their numeric values.
688 * Expand a branch followed by `.' to the latest revision on that branch.
689 * Ignore `.' after a revision. Remove leading zeros.
690 * returns false on error;
693 return fexpandsym(source
, target
, (RILE
*)0);
697 fexpandsym(source
, target
, fp
)
701 /* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */
703 register char const *sp
, *bp
;
711 if (!sp
|| !*sp
) { /* Accept 0 pointer as a legal value. */
715 if (sp
[0] == KDELIM
&& !sp
[1]) {
718 if (!*prevrev
.string
) {
719 workerror("working file lacks revision number");
722 bufscpy(target
, prevrev
.string
);
725 tlim
= tp
+ target
->size
;
729 register char *p
= tp
;
730 size_t s
= tp
- target
->string
;
733 switch (ctab
[(unsigned char)*sp
]) {
741 p
= bufenlarge(target
, &tlim
);
751 p
= bufenlarge(target
, &tlim
);
753 tp
= target
->string
+ s
;
758 rcserror("Symbolic name `%s' is undefined.",tp
);
762 /* skip leading zeros */
763 for (bp
= tp
; *bp
=='0' && isdigit(bp
[1]); bp
++)
770 /* Insert default branch before initial `.'. */
778 getbranchno(b
, target
);
779 bp
= tp
= target
->string
;
780 tlim
= tp
+ target
->size
;
785 while ((*tp
++ = *bp
++))
787 tp
= bufenlarge(target
, &tlim
);
797 if (!(bp
= branchtip(target
->string
)))
809 rcserror("improper revision number: %s", source
);
814 namedrev(name
, delta
)
816 struct hshentry
*delta
;
817 /* Yield NAME if it names DELTA, 0 otherwise. */
820 char const *id
= 0, *p
, *val
;
821 for (p
= name
; ; p
++)
822 switch (ctab
[(unsigned char)*p
]) {
834 (val
= lookupsym(id
)) &&
835 strcmp(val
, delta
->num
) == 0
851 struct hshentries
*hs
;
853 h
= genrevs(branch
, (char*)0, (char*)0, (char*)0, &hs
);
854 return h
? h
->num
: (char const*)0;
860 return Dbranch
? branchtip(Dbranch
) : Head
? Head
->num
: (char const*)0;
868 * Test the routines that generate a sequence of delta numbers
869 * needed to regenerate a given delta.
872 char const cmdid
[] = "revtest";
876 int argc
; char * argv
[];
878 static struct buf numricrevno
;
879 char symrevno
[100]; /* used for input of revision numbers */
883 struct hshentries
*gendeltas
;
884 struct hshentry
* target
;
888 aputs("No input file\n",stderr
);
889 exitmain(EXIT_FAILURE
);
891 if (!(finptr
=Iopen(argv
[1], FOPEN_R
, (struct stat
*)0))) {
892 faterror("can't open input file %s", argv
[1]);
902 /* all output goes to stderr, to have diagnostics and */
903 /* errors in sequence. */
904 aputs("\nEnter revision number or <return> or '.': ",stderr
);
905 if (!fgets(symrevno
, 100, stdin
)) break;
906 if (*symrevno
== '.') break;
907 aprintf(stderr
,"%s;\n",symrevno
);
908 expandsym(symrevno
,&numricrevno
);
909 aprintf(stderr
,"expanded number: %s; ",numricrevno
.string
);
910 aprintf(stderr
,"Date: ");
911 fgets(date
, 20, stdin
); aprintf(stderr
,"%s; ",date
);
912 aprintf(stderr
,"Author: ");
913 fgets(author
, 20, stdin
); aprintf(stderr
,"%s; ",author
);
914 aprintf(stderr
,"State: ");
915 fgets(state
, 20, stdin
); aprintf(stderr
, "%s;\n", state
);
916 target
= genrevs(numricrevno
.string
, *date
?date
:(char *)0, *author
?author
:(char *)0,
917 *state
?state
:(char*)0, &gendeltas
);
920 aprintf(stderr
,"%s\n",gendeltas
->first
->num
);
921 gendeltas
= gendeltas
->next
;
925 aprintf(stderr
,"done\n");
926 exitmain(EXIT_SUCCESS
);
929 void exiterr() { _exit(EXIT_FAILURE
); }