rcsrev.c revision 10
1/*
2 *                     RCS revision number handling
3 */
4
5/* Copyright (C) 1982, 1988, 1989 Walter Tichy
6   Copyright 1990, 1991 by Paul Eggert
7   Distributed under license by the Free Software Foundation, Inc.
8
9This file is part of RCS.
10
11RCS is free software; you can redistribute it and/or modify
12it under the terms of the GNU General Public License as published by
13the Free Software Foundation; either version 2, or (at your option)
14any later version.
15
16RCS is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19GNU General Public License for more details.
20
21You should have received a copy of the GNU General Public License
22along with RCS; see the file COPYING.  If not, write to
23the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24
25Report problems and direct all questions to:
26
27    rcs-bugs@cs.purdue.edu
28
29*/
30
31
32
33
34/* $Log: rcsrev.c,v $
35 * Revision 5.3  1991/08/19  03:13:55  eggert
36 * Add `-r$', `-rB.'.  Remove botches like `<now>' from messages.  Tune.
37 *
38 * Revision 5.2  1991/04/21  11:58:28  eggert
39 * Add tiprev().
40 *
41 * Revision 5.1  1991/02/25  07:12:43  eggert
42 * Avoid overflow when comparing revision numbers.
43 *
44 * Revision 5.0  1990/08/22  08:13:43  eggert
45 * Remove compile-time limits; use malloc instead.
46 * Ansify and Posixate.  Tune.
47 * Remove possibility of an internal error.  Remove lint.
48 *
49 * Revision 4.5  89/05/01  15:13:22  narten
50 * changed copyright header to reflect current distribution rules
51 *
52 * Revision 4.4  87/12/18  11:45:22  narten
53 * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
54 * since there's now a return value there with a value. (Guy Harris)
55 *
56 * Revision 4.3  87/10/18  10:38:42  narten
57 * Updating version numbers. Changes relative to version 1.1 actually
58 * relative to 4.1
59 *
60 * Revision 1.3  87/09/24  14:00:37  narten
61 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
62 * warnings)
63 *
64 * Revision 1.2  87/03/27  14:22:37  jenkins
65 * Port to suns
66 *
67 * Revision 4.1  83/03/25  21:10:45  wft
68 * Only changed $Header to $Id.
69 *
70 * Revision 3.4  82/12/04  13:24:08  wft
71 * Replaced getdelta() with gettree().
72 *
73 * Revision 3.3  82/11/28  21:33:15  wft
74 * fixed compartial() and compnum() for nil-parameters; fixed nils
75 * in error messages. Testprogram output shortenend.
76 *
77 * Revision 3.2  82/10/18  21:19:47  wft
78 * renamed compnum->cmpnum, compnumfld->cmpnumfld,
79 * numericrevno->numricrevno.
80 *
81 * Revision 3.1  82/10/11  19:46:09  wft
82 * changed expandsym() to check for source==nil; returns zero length string
83 * in that case.
84 */
85
86
87
88/*
89#define REVTEST
90*/
91/* version REVTEST is for testing the routines that generate a sequence
92 * of delta numbers needed to regenerate a given delta.
93 */
94
95#include "rcsbase.h"
96
97libId(revId, "$Id: rcsrev.c,v 5.3 1991/08/19 03:13:55 eggert Exp $")
98
99static char const *branchtip P((char const*));
100static struct hshentry *genbranch P((struct hshentry const*,char const*,unsigned,char const*,char const*,char const*,struct hshentries**));
101
102
103
104	unsigned
105countnumflds(s)
106	char const *s;
107/* Given a pointer s to a dotted number (date or revision number),
108 * countnumflds returns the number of digitfields in s.
109 */
110{
111	register char const *sp;
112	register unsigned count;
113        if ((sp=s)==nil) return(0);
114        if (*sp == '\0') return(0);
115        count = 1;
116	do {
117                if (*sp++ == '.') count++;
118	} while (*sp);
119        return(count);
120}
121
122	void
123getbranchno(revno,branchno)
124	char const *revno;
125	struct buf *branchno;
126/* Given a non-nil revision number revno, getbranchno copies the number of the branch
127 * on which revno is into branchno. If revno itself is a branch number,
128 * it is copied unchanged.
129 */
130{
131	register unsigned numflds;
132	register char *tp;
133
134	bufscpy(branchno, revno);
135        numflds=countnumflds(revno);
136	if (!(numflds & 1)) {
137		tp = branchno->string;
138		while (--numflds)
139			while (*tp++ != '.')
140				;
141                *(tp-1)='\0';
142        }
143}
144
145
146
147int cmpnum(num1, num2)
148	char const *num1, *num2;
149/* compares the two dotted numbers num1 and num2 lexicographically
150 * by field. Individual fields are compared numerically.
151 * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
152 * omitted fields are assumed to be higher than the existing ones.
153*/
154{
155	register char const *s1, *s2;
156	register size_t d1, d2;
157	register int r;
158
159        s1=num1==nil?"":num1;
160        s2=num2==nil?"":num2;
161
162	for (;;) {
163		/* Give precedence to shorter one.  */
164		if (!*s1)
165			return (unsigned char)*s2;
166		if (!*s2)
167			return -1;
168
169		/* Strip leading zeros, then find number of digits.  */
170		while (*s1=='0') ++s1;  for (d1=0; isdigit(s1[d1]); d1++) ;
171		while (*s2=='0') ++s2;  for (d2=0; isdigit(s2[d2]); d2++) ;
172
173		/* Do not convert to integer; it might overflow!  */
174		if (d1 != d2)
175			return d1<d2 ? -1 : 1;
176		if ((r = memcmp(s1, s2, d1)))
177			return r;
178		s1 += d1;
179		s2 += d1;
180
181                /* skip '.' */
182		if (*s1) s1++;
183		if (*s2) s2++;
184	}
185}
186
187
188
189int cmpnumfld(num1, num2, fld)
190	char const *num1, *num2;
191	unsigned fld;
192/* Compare the two dotted numbers at field fld.
193 * num1 and num2 must have at least fld fields.
194 * fld must be positive.
195*/
196{
197	register char const *s1, *s2;
198	register size_t d1, d2;
199
200	s1 = num1;
201	s2 = num2;
202        /* skip fld-1 fields */
203	while (--fld) {
204		while (*s1++ != '.')
205			;
206		while (*s2++ != '.')
207			;
208	}
209        /* Now s1 and s2 point to the beginning of the respective fields */
210	while (*s1=='0') ++s1;  for (d1=0; isdigit(s1[d1]); d1++) ;
211	while (*s2=='0') ++s2;  for (d2=0; isdigit(s2[d2]); d2++) ;
212
213	return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
214}
215
216
217	static void
218cantfindbranch(revno, date, author, state)
219	char const *revno, date[datesize], *author, *state;
220{
221	char datebuf[datesize];
222
223	error("No revision on branch %s has%s%s%s%s%s%s.",
224		revno,
225		date ? " a date before " : "",
226		date ? date2str(date,datebuf) : "",
227		author ? " and author "+(date?0:4) : "",
228		author ? author : "",
229		state ? " and state "+(date||author?0:4) : "",
230		state ? state : ""
231	);
232}
233
234	static void
235absent(revno, field)
236	char const *revno;
237	unsigned field;
238{
239	struct buf t;
240	bufautobegin(&t);
241	error("%s %s absent", field&1?"revision":"branch",
242		partialno(&t,revno,field)
243	);
244	bufautoend(&t);
245}
246
247
248	int
249compartial(num1, num2, length)
250	char const *num1, *num2;
251	unsigned length;
252
253/*   compare the first "length" fields of two dot numbers;
254     the omitted field is considered to be larger than any number  */
255/*   restriction:  at least one number has length or more fields   */
256
257{
258	register char const *s1, *s2;
259	register size_t d1, d2;
260	register int r;
261
262        s1 = num1;      s2 = num2;
263	if (!s1) return 1;
264	if (!s2) return -1;
265
266	for (;;) {
267	    if (!*s1) return 1;
268	    if (!*s2) return -1;
269
270	    while (*s1=='0') ++s1;  for (d1=0; isdigit(s1[d1]); d1++) ;
271	    while (*s2=='0') ++s2;  for (d2=0; isdigit(s2[d2]); d2++) ;
272
273	    if (d1 != d2)
274		    return d1<d2 ? -1 : 1;
275	    if ((r = memcmp(s1, s2, d1)))
276		    return r;
277	    s1 += d1;
278	    s2 += d1;
279
280	    if (*s1 == '.') s1++;
281            if (*s2 == '.') s2++;
282
283	    if ( --length == 0 ) return 0;
284	}
285}
286
287
288char * partialno(rev1,rev2,length)
289	struct buf *rev1;
290	char const *rev2;
291	register unsigned length;
292/* Function: Copies length fields of revision number rev2 into rev1.
293 * Return rev1's string.
294 */
295{
296	register char *r1;
297
298	bufscpy(rev1, rev2);
299	r1 = rev1->string;
300        while (length) {
301		while (*r1!='.' && *r1)
302			++r1;
303		++r1;
304                length--;
305        }
306        /* eliminate last '.'*/
307        *(r1-1)='\0';
308	return rev1->string;
309}
310
311
312
313
314	static void
315store1(store, next)
316	struct hshentries ***store;
317	struct hshentry *next;
318/*
319 * Allocate a new list node that addresses NEXT.
320 * Append it to the list that **STORE is the end pointer of.
321 */
322{
323	register struct hshentries *p;
324
325	p = ftalloc(struct hshentries);
326	p->first = next;
327	**store = p;
328	*store = &p->rest;
329}
330
331struct hshentry * genrevs(revno,date,author,state,store)
332	char const *revno, *date, *author, *state;
333	struct hshentries **store;
334/* Function: finds the deltas needed for reconstructing the
335 * revision given by revno, date, author, and state, and stores pointers
336 * to these deltas into a list whose starting address is given by store.
337 * The last delta (target delta) is returned.
338 * If the proper delta could not be found, nil is returned.
339 */
340{
341	unsigned length;
342        register struct hshentry * next;
343        int result;
344	char const *branchnum;
345	struct buf t;
346	char datebuf[datesize];
347
348	bufautobegin(&t);
349
350	if (!(next = Head)) {
351		error("RCS file empty");
352		goto norev;
353        }
354
355        length = countnumflds(revno);
356
357        if (length >= 1) {
358                /* at least one field; find branch exactly */
359		while ((result=cmpnumfld(revno,next->num,1)) < 0) {
360			store1(&store, next);
361                        next = next->next;
362			if (!next) {
363			    error("branch number %s too low", partialno(&t,revno,1));
364			    goto norev;
365			}
366                }
367
368		if (result>0) {
369			absent(revno, 1);
370			goto norev;
371		}
372        }
373        if (length<=1){
374                /* pick latest one on given branch */
375                branchnum = next->num; /* works even for empty revno*/
376                while ((next!=nil) &&
377                       (cmpnumfld(branchnum,next->num,1)==0) &&
378                       !(
379                        (date==nil?1:(cmpnum(date,next->date)>=0)) &&
380                        (author==nil?1:(strcmp(author,next->author)==0)) &&
381                        (state ==nil?1:(strcmp(state, next->state) ==0))
382                        )
383                       )
384		{
385			store1(&store, next);
386                        next=next->next;
387                }
388                if ((next==nil) ||
389                    (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
390			cantfindbranch(
391				length ? revno : partialno(&t,branchnum,1),
392				date, author, state
393			);
394			goto norev;
395                } else {
396			store1(&store, next);
397                }
398                *store = nil;
399                return next;
400        }
401
402        /* length >=2 */
403        /* find revision; may go low if length==2*/
404	while ((result=cmpnumfld(revno,next->num,2)) < 0  &&
405               (cmpnumfld(revno,next->num,1)==0) ) {
406		store1(&store, next);
407                next = next->next;
408		if (!next)
409			break;
410        }
411
412        if ((next==nil) || (cmpnumfld(revno,next->num,1)!=0)) {
413		error("revision number %s too low", partialno(&t,revno,2));
414		goto norev;
415        }
416        if ((length>2) && (result!=0)) {
417		absent(revno, 2);
418		goto norev;
419        }
420
421        /* print last one */
422	store1(&store, next);
423
424        if (length>2)
425                return genbranch(next,revno,length,date,author,state,store);
426        else { /* length == 2*/
427                if ((date!=nil) && (cmpnum(date,next->date)<0)){
428			error("Revision %s has date %s.",
429				next->num,
430				date2str(next->date, datebuf)
431			);
432                        return nil;
433                }
434                if ((author!=nil)&&(strcmp(author,next->author)!=0)) {
435                        error("Revision %s has author %s.",next->num,next->author);
436                        return nil;
437                }
438                if ((state!=nil)&&(strcmp(state,next->state)!=0)) {
439                        error("Revision %s has state %s.",next->num,
440                               next->state==nil?"<empty>":next->state);
441                        return nil;
442                }
443                *store=nil;
444                return next;
445        }
446
447    norev:
448	bufautoend(&t);
449	return nil;
450}
451
452
453
454
455	static struct hshentry *
456genbranch(bpoint, revno, length, date, author, state, store)
457	struct hshentry const *bpoint;
458	char const *revno;
459	unsigned length;
460	char const *date, *author, *state;
461	struct hshentries **store;
462/* Function: given a branchpoint, a revision number, date, author, and state,
463 * genbranch finds the deltas necessary to reconstruct the given revision
464 * from the branch point on.
465 * Pointers to the found deltas are stored in a list beginning with store.
466 * revno must be on a side branch.
467 * return nil on error
468 */
469{
470	unsigned field;
471        register struct hshentry * next, * trail;
472	register struct branchhead const *bhead;
473        int result;
474	struct buf t;
475	char datebuf[datesize];
476
477	field = 3;
478        bhead = bpoint->branches;
479
480	do {
481		if (!bhead) {
482			bufautobegin(&t);
483			error("no side branches present for %s", partialno(&t,revno,field-1));
484			bufautoend(&t);
485			return nil;
486		}
487
488                /*find branch head*/
489                /*branches are arranged in increasing order*/
490		while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
491                        bhead = bhead->nextbranch;
492			if (!bhead) {
493			    bufautobegin(&t);
494			    error("branch number %s too high",partialno(&t,revno,field));
495			    bufautoend(&t);
496			    return nil;
497			}
498                }
499
500		if (result<0) {
501		    absent(revno, field);
502		    return nil;
503		}
504
505                next = bhead->hsh;
506                if (length==field) {
507                        /* pick latest one on that branch */
508                        trail=nil;
509                        do { if ((date==nil?1:(cmpnum(date,next->date)>=0)) &&
510                                 (author==nil?1:(strcmp(author,next->author)==0)) &&
511                                 (state ==nil?1:(strcmp(state, next->state) ==0))
512                             ) trail = next;
513                             next=next->next;
514                        } while (next!=nil);
515
516                        if (trail==nil) {
517			     cantfindbranch(revno, date, author, state);
518                             return nil;
519                        } else { /* print up to last one suitable */
520                             next = bhead->hsh;
521                             while (next!=trail) {
522				  store1(&store, next);
523                                  next=next->next;
524                             }
525			     store1(&store, next);
526                        }
527			*store = nil;
528                        return next;
529                }
530
531                /* length > field */
532                /* find revision */
533                /* check low */
534                if (cmpnumfld(revno,next->num,field+1)<0) {
535			bufautobegin(&t);
536			error("revision number %s too low", partialno(&t,revno,field+1));
537			bufautoend(&t);
538                        return(nil);
539                }
540		do {
541			store1(&store, next);
542                        trail = next;
543                        next = next->next;
544                } while ((next!=nil) &&
545                       (cmpnumfld(revno,next->num,field+1) >=0));
546
547                if ((length>field+1) &&  /*need exact hit */
548                    (cmpnumfld(revno,trail->num,field+1) !=0)){
549			absent(revno, field+1);
550                        return(nil);
551                }
552                if (length == field+1) {
553                        if ((date!=nil) && (cmpnum(date,trail->date)<0)){
554				error("Revision %s has date %s.",
555					trail->num,
556					date2str(trail->date, datebuf)
557				);
558                                return nil;
559                        }
560                        if ((author!=nil)&&(strcmp(author,trail->author)!=0)) {
561                                error("Revision %s has author %s.",trail->num,trail->author);
562                                return nil;
563                        }
564                        if ((state!=nil)&&(strcmp(state,trail->state)!=0)) {
565                                error("Revision %s has state %s.",trail->num,
566                                       trail->state==nil?"<empty>":trail->state);
567                                return nil;
568                        }
569                }
570                bhead = trail->branches;
571
572	} while ((field+=2) <= length);
573        * store = nil;
574        return trail;
575}
576
577
578	static char const *
579lookupsym(id)
580	char const *id;
581/* Function: looks up id in the list of symbolic names starting
582 * with pointer SYMBOLS, and returns a pointer to the corresponding
583 * revision number. Returns nil if not present.
584 */
585{
586	register struct assoc const *next;
587        next = Symbols;
588        while (next!=nil) {
589                if (strcmp(id, next->symbol)==0)
590			return next->num;
591                else    next=next->nextassoc;
592        }
593        return nil;
594}
595
596int expandsym(source, target)
597	char const *source;
598	struct buf *target;
599/* Function: Source points to a revision number. Expandsym copies
600 * the number to target, but replaces all symbolic fields in the
601 * source number with their numeric values.
602 * Expand a branch followed by `.' to the latest revision on that branch.
603 * Ignore `.' after a revision.  Remove leading zeros.
604 * returns false on error;
605 */
606{
607	return fexpandsym(source, target, (RILE*)0);
608}
609
610	int
611fexpandsym(source, target, fp)
612	char const *source;
613	struct buf *target;
614	RILE *fp;
615/* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM.  */
616{
617	register char const *sp, *bp;
618	register char *tp;
619	char const *tlim;
620        register enum tokens d;
621	unsigned dots;
622
623	sp = source;
624	bufalloc(target, 1);
625	tp = target->string;
626	if (!sp || !*sp) { /*accept nil pointer as a legal value*/
627                *tp='\0';
628                return true;
629        }
630	if (sp[0] == KDELIM  &&  !sp[1]) {
631		if (!getoldkeys(fp))
632			return false;
633		if (!*prevrev.string) {
634			error("working file lacks revision number");
635			return false;
636		}
637		bufscpy(target, prevrev.string);
638		return true;
639	}
640	tlim = tp + target->size;
641	dots = 0;
642
643	for (;;) {
644		switch (ctab[(unsigned char)*sp]) {
645		    case DIGIT:
646			while (*sp=='0' && isdigit(sp[1]))
647                                /* skip leading zeroes */
648                                sp++;
649			do {
650				if (tlim <= tp)
651					tp = bufenlarge(target, &tlim);
652			} while (isdigit(*tp++ = *sp++));
653			--sp;
654			tp[-1] = '\0';
655			break;
656
657		    case LETTER:
658		    case Letter:
659			{
660			register char *p = tp;
661			register size_t s = tp - target->string;
662			do {
663				if (tlim <= p)
664					p = bufenlarge(target, &tlim);
665				*p++ = *sp++;
666			} while ((d=ctab[(unsigned char)*sp])==LETTER ||
667			      d==Letter || d==DIGIT ||
668                              (d==IDCHAR));
669			if (tlim <= p)
670				p = bufenlarge(target, &tlim);
671			*p = 0;
672			tp = target->string + s;
673			}
674			bp = lookupsym(tp);
675                        if (bp==nil) {
676				error("Symbolic number %s is undefined.", tp);
677                                return false;
678                        }
679			do {
680				if (tlim <= tp)
681					tp = bufenlarge(target, &tlim);
682			} while ((*tp++ = *bp++));
683			break;
684
685		    default:
686			goto improper;
687                }
688		switch (*sp++) {
689		    case '\0': return true;
690		    case '.': break;
691		    default: goto improper;
692		}
693		if (!*sp) {
694			if (dots & 1)
695				goto improper;
696			if (!(bp = branchtip(target->string)))
697				return false;
698			bufscpy(target, bp);
699			return true;
700		}
701		++dots;
702		tp[-1] = '.';
703        }
704
705    improper:
706	error("improper revision number: %s", source);
707	return false;
708}
709
710	static char const *
711branchtip(branch)
712	char const *branch;
713{
714	struct hshentry *h;
715	struct hshentries *hs;
716
717	h  =  genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
718	return h ? h->num : (char const*)0;
719}
720
721	char const *
722tiprev()
723{
724	return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
725}
726
727
728
729#ifdef REVTEST
730
731char const cmdid[] = "revtest";
732
733	int
734main(argc,argv)
735int argc; char * argv[];
736{
737	static struct buf numricrevno;
738	char symrevno[100];       /* used for input of revision numbers */
739        char author[20];
740        char state[20];
741        char date[20];
742	struct hshentries *gendeltas;
743        struct hshentry * target;
744        int i;
745
746        if (argc<2) {
747		aputs("No input file\n",stderr);
748		exitmain(EXIT_FAILURE);
749        }
750	if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
751		faterror("can't open input file %s", argv[1]);
752        }
753        Lexinit();
754        getadmin();
755
756        gettree();
757
758        getdesc(false);
759
760        do {
761                /* all output goes to stderr, to have diagnostics and       */
762                /* errors in sequence.                                      */
763		aputs("\nEnter revision number or <return> or '.': ",stderr);
764		if (!gets(symrevno)) break;
765                if (*symrevno == '.') break;
766		aprintf(stderr,"%s;\n",symrevno);
767		expandsym(symrevno,&numricrevno);
768		aprintf(stderr,"expanded number: %s; ",numricrevno.string);
769		aprintf(stderr,"Date: ");
770		gets(date); aprintf(stderr,"%s; ",date);
771		aprintf(stderr,"Author: ");
772		gets(author); aprintf(stderr,"%s; ",author);
773		aprintf(stderr,"State: ");
774		gets(state); aprintf(stderr, "%s;\n", state);
775		target = genrevs(numricrevno.string, *date?date:(char *)nil, *author?author:(char *)nil,
776				 *state?state:(char*)nil, &gendeltas);
777                if (target!=nil) {
778			while (gendeltas) {
779				aprintf(stderr,"%s\n",gendeltas->first->num);
780				gendeltas = gendeltas->next;
781                        }
782                }
783        } while (true);
784	aprintf(stderr,"done\n");
785	exitmain(EXIT_SUCCESS);
786}
787
788exiting void exiterr() { _exit(EXIT_FAILURE); }
789
790#endif
791