rcsrev.c revision 9
19Sjkh/*
29Sjkh *                     RCS revision number handling
39Sjkh */
49Sjkh
59Sjkh/* Copyright (C) 1982, 1988, 1989 Walter Tichy
69Sjkh   Copyright 1990, 1991 by Paul Eggert
79Sjkh   Distributed under license by the Free Software Foundation, Inc.
89Sjkh
99SjkhThis file is part of RCS.
109Sjkh
119SjkhRCS is free software; you can redistribute it and/or modify
129Sjkhit under the terms of the GNU General Public License as published by
139Sjkhthe Free Software Foundation; either version 2, or (at your option)
149Sjkhany later version.
159Sjkh
169SjkhRCS is distributed in the hope that it will be useful,
179Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of
189SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
199SjkhGNU General Public License for more details.
209Sjkh
219SjkhYou should have received a copy of the GNU General Public License
229Sjkhalong with RCS; see the file COPYING.  If not, write to
239Sjkhthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
249Sjkh
259SjkhReport problems and direct all questions to:
269Sjkh
279Sjkh    rcs-bugs@cs.purdue.edu
289Sjkh
299Sjkh*/
309Sjkh
319Sjkh
329Sjkh
339Sjkh
349Sjkh/* $Log: rcsrev.c,v $
359Sjkh * Revision 5.3  1991/08/19  03:13:55  eggert
369Sjkh * Add `-r$', `-rB.'.  Remove botches like `<now>' from messages.  Tune.
379Sjkh *
389Sjkh * Revision 5.2  1991/04/21  11:58:28  eggert
399Sjkh * Add tiprev().
409Sjkh *
419Sjkh * Revision 5.1  1991/02/25  07:12:43  eggert
429Sjkh * Avoid overflow when comparing revision numbers.
439Sjkh *
449Sjkh * Revision 5.0  1990/08/22  08:13:43  eggert
459Sjkh * Remove compile-time limits; use malloc instead.
469Sjkh * Ansify and Posixate.  Tune.
479Sjkh * Remove possibility of an internal error.  Remove lint.
489Sjkh *
499Sjkh * Revision 4.5  89/05/01  15:13:22  narten
509Sjkh * changed copyright header to reflect current distribution rules
519Sjkh *
529Sjkh * Revision 4.4  87/12/18  11:45:22  narten
539Sjkh * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
549Sjkh * since there's now a return value there with a value. (Guy Harris)
559Sjkh *
569Sjkh * Revision 4.3  87/10/18  10:38:42  narten
579Sjkh * Updating version numbers. Changes relative to version 1.1 actually
589Sjkh * relative to 4.1
599Sjkh *
609Sjkh * Revision 1.3  87/09/24  14:00:37  narten
619Sjkh * Sources now pass through lint (if you ignore printf/sprintf/fprintf
629Sjkh * warnings)
639Sjkh *
649Sjkh * Revision 1.2  87/03/27  14:22:37  jenkins
659Sjkh * Port to suns
669Sjkh *
679Sjkh * Revision 4.1  83/03/25  21:10:45  wft
689Sjkh * Only changed $Header to $Id.
699Sjkh *
709Sjkh * Revision 3.4  82/12/04  13:24:08  wft
719Sjkh * Replaced getdelta() with gettree().
729Sjkh *
739Sjkh * Revision 3.3  82/11/28  21:33:15  wft
749Sjkh * fixed compartial() and compnum() for nil-parameters; fixed nils
759Sjkh * in error messages. Testprogram output shortenend.
769Sjkh *
779Sjkh * Revision 3.2  82/10/18  21:19:47  wft
789Sjkh * renamed compnum->cmpnum, compnumfld->cmpnumfld,
799Sjkh * numericrevno->numricrevno.
809Sjkh *
819Sjkh * Revision 3.1  82/10/11  19:46:09  wft
829Sjkh * changed expandsym() to check for source==nil; returns zero length string
839Sjkh * in that case.
849Sjkh */
859Sjkh
869Sjkh
879Sjkh
889Sjkh/*
899Sjkh#define REVTEST
909Sjkh*/
919Sjkh/* version REVTEST is for testing the routines that generate a sequence
929Sjkh * of delta numbers needed to regenerate a given delta.
939Sjkh */
949Sjkh
959Sjkh#include "rcsbase.h"
969Sjkh
979SjkhlibId(revId, "$Id: rcsrev.c,v 5.3 1991/08/19 03:13:55 eggert Exp $")
989Sjkh
999Sjkhstatic char const *branchtip P((char const*));
1009Sjkhstatic struct hshentry *genbranch P((struct hshentry const*,char const*,unsigned,char const*,char const*,char const*,struct hshentries**));
1019Sjkh
1029Sjkh
1039Sjkh
1049Sjkh	unsigned
1059Sjkhcountnumflds(s)
1069Sjkh	char const *s;
1079Sjkh/* Given a pointer s to a dotted number (date or revision number),
1089Sjkh * countnumflds returns the number of digitfields in s.
1099Sjkh */
1109Sjkh{
1119Sjkh	register char const *sp;
1129Sjkh	register unsigned count;
1139Sjkh        if ((sp=s)==nil) return(0);
1149Sjkh        if (*sp == '\0') return(0);
1159Sjkh        count = 1;
1169Sjkh	do {
1179Sjkh                if (*sp++ == '.') count++;
1189Sjkh	} while (*sp);
1199Sjkh        return(count);
1209Sjkh}
1219Sjkh
1229Sjkh	void
1239Sjkhgetbranchno(revno,branchno)
1249Sjkh	char const *revno;
1259Sjkh	struct buf *branchno;
1269Sjkh/* Given a non-nil revision number revno, getbranchno copies the number of the branch
1279Sjkh * on which revno is into branchno. If revno itself is a branch number,
1289Sjkh * it is copied unchanged.
1299Sjkh */
1309Sjkh{
1319Sjkh	register unsigned numflds;
1329Sjkh	register char *tp;
1339Sjkh
1349Sjkh	bufscpy(branchno, revno);
1359Sjkh        numflds=countnumflds(revno);
1369Sjkh	if (!(numflds & 1)) {
1379Sjkh		tp = branchno->string;
1389Sjkh		while (--numflds)
1399Sjkh			while (*tp++ != '.')
1409Sjkh				;
1419Sjkh                *(tp-1)='\0';
1429Sjkh        }
1439Sjkh}
1449Sjkh
1459Sjkh
1469Sjkh
1479Sjkhint cmpnum(num1, num2)
1489Sjkh	char const *num1, *num2;
1499Sjkh/* compares the two dotted numbers num1 and num2 lexicographically
1509Sjkh * by field. Individual fields are compared numerically.
1519Sjkh * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
1529Sjkh * omitted fields are assumed to be higher than the existing ones.
1539Sjkh*/
1549Sjkh{
1559Sjkh	register char const *s1, *s2;
1569Sjkh	register size_t d1, d2;
1579Sjkh	register int r;
1589Sjkh
1599Sjkh        s1=num1==nil?"":num1;
1609Sjkh        s2=num2==nil?"":num2;
1619Sjkh
1629Sjkh	for (;;) {
1639Sjkh		/* Give precedence to shorter one.  */
1649Sjkh		if (!*s1)
1659Sjkh			return (unsigned char)*s2;
1669Sjkh		if (!*s2)
1679Sjkh			return -1;
1689Sjkh
1699Sjkh		/* Strip leading zeros, then find number of digits.  */
1709Sjkh		while (*s1=='0') ++s1;  for (d1=0; isdigit(s1[d1]); d1++) ;
1719Sjkh		while (*s2=='0') ++s2;  for (d2=0; isdigit(s2[d2]); d2++) ;
1729Sjkh
1739Sjkh		/* Do not convert to integer; it might overflow!  */
1749Sjkh		if (d1 != d2)
1759Sjkh			return d1<d2 ? -1 : 1;
1769Sjkh		if ((r = memcmp(s1, s2, d1)))
1779Sjkh			return r;
1789Sjkh		s1 += d1;
1799Sjkh		s2 += d1;
1809Sjkh
1819Sjkh                /* skip '.' */
1829Sjkh		if (*s1) s1++;
1839Sjkh		if (*s2) s2++;
1849Sjkh	}
1859Sjkh}
1869Sjkh
1879Sjkh
1889Sjkh
1899Sjkhint cmpnumfld(num1, num2, fld)
1909Sjkh	char const *num1, *num2;
1919Sjkh	unsigned fld;
1929Sjkh/* Compare the two dotted numbers at field fld.
1939Sjkh * num1 and num2 must have at least fld fields.
1949Sjkh * fld must be positive.
1959Sjkh*/
1969Sjkh{
1979Sjkh	register char const *s1, *s2;
1989Sjkh	register size_t d1, d2;
1999Sjkh
2009Sjkh	s1 = num1;
2019Sjkh	s2 = num2;
2029Sjkh        /* skip fld-1 fields */
2039Sjkh	while (--fld) {
2049Sjkh		while (*s1++ != '.')
2059Sjkh			;
2069Sjkh		while (*s2++ != '.')
2079Sjkh			;
2089Sjkh	}
2099Sjkh        /* Now s1 and s2 point to the beginning of the respective fields */
2109Sjkh	while (*s1=='0') ++s1;  for (d1=0; isdigit(s1[d1]); d1++) ;
2119Sjkh	while (*s2=='0') ++s2;  for (d2=0; isdigit(s2[d2]); d2++) ;
2129Sjkh
2139Sjkh	return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
2149Sjkh}
2159Sjkh
2169Sjkh
2179Sjkh	static void
2189Sjkhcantfindbranch(revno, date, author, state)
2199Sjkh	char const *revno, date[datesize], *author, *state;
2209Sjkh{
2219Sjkh	char datebuf[datesize];
2229Sjkh
2239Sjkh	error("No revision on branch %s has%s%s%s%s%s%s.",
2249Sjkh		revno,
2259Sjkh		date ? " a date before " : "",
2269Sjkh		date ? date2str(date,datebuf) : "",
2279Sjkh		author ? " and author "+(date?0:4) : "",
2289Sjkh		author ? author : "",
2299Sjkh		state ? " and state "+(date||author?0:4) : "",
2309Sjkh		state ? state : ""
2319Sjkh	);
2329Sjkh}
2339Sjkh
2349Sjkh	static void
2359Sjkhabsent(revno, field)
2369Sjkh	char const *revno;
2379Sjkh	unsigned field;
2389Sjkh{
2399Sjkh	struct buf t;
2409Sjkh	bufautobegin(&t);
2419Sjkh	error("%s %s absent", field&1?"revision":"branch",
2429Sjkh		partialno(&t,revno,field)
2439Sjkh	);
2449Sjkh	bufautoend(&t);
2459Sjkh}
2469Sjkh
2479Sjkh
2489Sjkh	int
2499Sjkhcompartial(num1, num2, length)
2509Sjkh	char const *num1, *num2;
2519Sjkh	unsigned length;
2529Sjkh
2539Sjkh/*   compare the first "length" fields of two dot numbers;
2549Sjkh     the omitted field is considered to be larger than any number  */
2559Sjkh/*   restriction:  at least one number has length or more fields   */
2569Sjkh
2579Sjkh{
2589Sjkh	register char const *s1, *s2;
2599Sjkh	register size_t d1, d2;
2609Sjkh	register int r;
2619Sjkh
2629Sjkh        s1 = num1;      s2 = num2;
2639Sjkh	if (!s1) return 1;
2649Sjkh	if (!s2) return -1;
2659Sjkh
2669Sjkh	for (;;) {
2679Sjkh	    if (!*s1) return 1;
2689Sjkh	    if (!*s2) return -1;
2699Sjkh
2709Sjkh	    while (*s1=='0') ++s1;  for (d1=0; isdigit(s1[d1]); d1++) ;
2719Sjkh	    while (*s2=='0') ++s2;  for (d2=0; isdigit(s2[d2]); d2++) ;
2729Sjkh
2739Sjkh	    if (d1 != d2)
2749Sjkh		    return d1<d2 ? -1 : 1;
2759Sjkh	    if ((r = memcmp(s1, s2, d1)))
2769Sjkh		    return r;
2779Sjkh	    s1 += d1;
2789Sjkh	    s2 += d1;
2799Sjkh
2809Sjkh	    if (*s1 == '.') s1++;
2819Sjkh            if (*s2 == '.') s2++;
2829Sjkh
2839Sjkh	    if ( --length == 0 ) return 0;
2849Sjkh	}
2859Sjkh}
2869Sjkh
2879Sjkh
2889Sjkhchar * partialno(rev1,rev2,length)
2899Sjkh	struct buf *rev1;
2909Sjkh	char const *rev2;
2919Sjkh	register unsigned length;
2929Sjkh/* Function: Copies length fields of revision number rev2 into rev1.
2939Sjkh * Return rev1's string.
2949Sjkh */
2959Sjkh{
2969Sjkh	register char *r1;
2979Sjkh
2989Sjkh	bufscpy(rev1, rev2);
2999Sjkh	r1 = rev1->string;
3009Sjkh        while (length) {
3019Sjkh		while (*r1!='.' && *r1)
3029Sjkh			++r1;
3039Sjkh		++r1;
3049Sjkh                length--;
3059Sjkh        }
3069Sjkh        /* eliminate last '.'*/
3079Sjkh        *(r1-1)='\0';
3089Sjkh	return rev1->string;
3099Sjkh}
3109Sjkh
3119Sjkh
3129Sjkh
3139Sjkh
3149Sjkh	static void
3159Sjkhstore1(store, next)
3169Sjkh	struct hshentries ***store;
3179Sjkh	struct hshentry *next;
3189Sjkh/*
3199Sjkh * Allocate a new list node that addresses NEXT.
3209Sjkh * Append it to the list that **STORE is the end pointer of.
3219Sjkh */
3229Sjkh{
3239Sjkh	register struct hshentries *p;
3249Sjkh
3259Sjkh	p = ftalloc(struct hshentries);
3269Sjkh	p->first = next;
3279Sjkh	**store = p;
3289Sjkh	*store = &p->rest;
3299Sjkh}
3309Sjkh
3319Sjkhstruct hshentry * genrevs(revno,date,author,state,store)
3329Sjkh	char const *revno, *date, *author, *state;
3339Sjkh	struct hshentries **store;
3349Sjkh/* Function: finds the deltas needed for reconstructing the
3359Sjkh * revision given by revno, date, author, and state, and stores pointers
3369Sjkh * to these deltas into a list whose starting address is given by store.
3379Sjkh * The last delta (target delta) is returned.
3389Sjkh * If the proper delta could not be found, nil is returned.
3399Sjkh */
3409Sjkh{
3419Sjkh	unsigned length;
3429Sjkh        register struct hshentry * next;
3439Sjkh        int result;
3449Sjkh	char const *branchnum;
3459Sjkh	struct buf t;
3469Sjkh	char datebuf[datesize];
3479Sjkh
3489Sjkh	bufautobegin(&t);
3499Sjkh
3509Sjkh	if (!(next = Head)) {
3519Sjkh		error("RCS file empty");
3529Sjkh		goto norev;
3539Sjkh        }
3549Sjkh
3559Sjkh        length = countnumflds(revno);
3569Sjkh
3579Sjkh        if (length >= 1) {
3589Sjkh                /* at least one field; find branch exactly */
3599Sjkh		while ((result=cmpnumfld(revno,next->num,1)) < 0) {
3609Sjkh			store1(&store, next);
3619Sjkh                        next = next->next;
3629Sjkh			if (!next) {
3639Sjkh			    error("branch number %s too low", partialno(&t,revno,1));
3649Sjkh			    goto norev;
3659Sjkh			}
3669Sjkh                }
3679Sjkh
3689Sjkh		if (result>0) {
3699Sjkh			absent(revno, 1);
3709Sjkh			goto norev;
3719Sjkh		}
3729Sjkh        }
3739Sjkh        if (length<=1){
3749Sjkh                /* pick latest one on given branch */
3759Sjkh                branchnum = next->num; /* works even for empty revno*/
3769Sjkh                while ((next!=nil) &&
3779Sjkh                       (cmpnumfld(branchnum,next->num,1)==0) &&
3789Sjkh                       !(
3799Sjkh                        (date==nil?1:(cmpnum(date,next->date)>=0)) &&
3809Sjkh                        (author==nil?1:(strcmp(author,next->author)==0)) &&
3819Sjkh                        (state ==nil?1:(strcmp(state, next->state) ==0))
3829Sjkh                        )
3839Sjkh                       )
3849Sjkh		{
3859Sjkh			store1(&store, next);
3869Sjkh                        next=next->next;
3879Sjkh                }
3889Sjkh                if ((next==nil) ||
3899Sjkh                    (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
3909Sjkh			cantfindbranch(
3919Sjkh				length ? revno : partialno(&t,branchnum,1),
3929Sjkh				date, author, state
3939Sjkh			);
3949Sjkh			goto norev;
3959Sjkh                } else {
3969Sjkh			store1(&store, next);
3979Sjkh                }
3989Sjkh                *store = nil;
3999Sjkh                return next;
4009Sjkh        }
4019Sjkh
4029Sjkh        /* length >=2 */
4039Sjkh        /* find revision; may go low if length==2*/
4049Sjkh	while ((result=cmpnumfld(revno,next->num,2)) < 0  &&
4059Sjkh               (cmpnumfld(revno,next->num,1)==0) ) {
4069Sjkh		store1(&store, next);
4079Sjkh                next = next->next;
4089Sjkh		if (!next)
4099Sjkh			break;
4109Sjkh        }
4119Sjkh
4129Sjkh        if ((next==nil) || (cmpnumfld(revno,next->num,1)!=0)) {
4139Sjkh		error("revision number %s too low", partialno(&t,revno,2));
4149Sjkh		goto norev;
4159Sjkh        }
4169Sjkh        if ((length>2) && (result!=0)) {
4179Sjkh		absent(revno, 2);
4189Sjkh		goto norev;
4199Sjkh        }
4209Sjkh
4219Sjkh        /* print last one */
4229Sjkh	store1(&store, next);
4239Sjkh
4249Sjkh        if (length>2)
4259Sjkh                return genbranch(next,revno,length,date,author,state,store);
4269Sjkh        else { /* length == 2*/
4279Sjkh                if ((date!=nil) && (cmpnum(date,next->date)<0)){
4289Sjkh			error("Revision %s has date %s.",
4299Sjkh				next->num,
4309Sjkh				date2str(next->date, datebuf)
4319Sjkh			);
4329Sjkh                        return nil;
4339Sjkh                }
4349Sjkh                if ((author!=nil)&&(strcmp(author,next->author)!=0)) {
4359Sjkh                        error("Revision %s has author %s.",next->num,next->author);
4369Sjkh                        return nil;
4379Sjkh                }
4389Sjkh                if ((state!=nil)&&(strcmp(state,next->state)!=0)) {
4399Sjkh                        error("Revision %s has state %s.",next->num,
4409Sjkh                               next->state==nil?"<empty>":next->state);
4419Sjkh                        return nil;
4429Sjkh                }
4439Sjkh                *store=nil;
4449Sjkh                return next;
4459Sjkh        }
4469Sjkh
4479Sjkh    norev:
4489Sjkh	bufautoend(&t);
4499Sjkh	return nil;
4509Sjkh}
4519Sjkh
4529Sjkh
4539Sjkh
4549Sjkh
4559Sjkh	static struct hshentry *
4569Sjkhgenbranch(bpoint, revno, length, date, author, state, store)
4579Sjkh	struct hshentry const *bpoint;
4589Sjkh	char const *revno;
4599Sjkh	unsigned length;
4609Sjkh	char const *date, *author, *state;
4619Sjkh	struct hshentries **store;
4629Sjkh/* Function: given a branchpoint, a revision number, date, author, and state,
4639Sjkh * genbranch finds the deltas necessary to reconstruct the given revision
4649Sjkh * from the branch point on.
4659Sjkh * Pointers to the found deltas are stored in a list beginning with store.
4669Sjkh * revno must be on a side branch.
4679Sjkh * return nil on error
4689Sjkh */
4699Sjkh{
4709Sjkh	unsigned field;
4719Sjkh        register struct hshentry * next, * trail;
4729Sjkh	register struct branchhead const *bhead;
4739Sjkh        int result;
4749Sjkh	struct buf t;
4759Sjkh	char datebuf[datesize];
4769Sjkh
4779Sjkh	field = 3;
4789Sjkh        bhead = bpoint->branches;
4799Sjkh
4809Sjkh	do {
4819Sjkh		if (!bhead) {
4829Sjkh			bufautobegin(&t);
4839Sjkh			error("no side branches present for %s", partialno(&t,revno,field-1));
4849Sjkh			bufautoend(&t);
4859Sjkh			return nil;
4869Sjkh		}
4879Sjkh
4889Sjkh                /*find branch head*/
4899Sjkh                /*branches are arranged in increasing order*/
4909Sjkh		while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
4919Sjkh                        bhead = bhead->nextbranch;
4929Sjkh			if (!bhead) {
4939Sjkh			    bufautobegin(&t);
4949Sjkh			    error("branch number %s too high",partialno(&t,revno,field));
4959Sjkh			    bufautoend(&t);
4969Sjkh			    return nil;
4979Sjkh			}
4989Sjkh                }
4999Sjkh
5009Sjkh		if (result<0) {
5019Sjkh		    absent(revno, field);
5029Sjkh		    return nil;
5039Sjkh		}
5049Sjkh
5059Sjkh                next = bhead->hsh;
5069Sjkh                if (length==field) {
5079Sjkh                        /* pick latest one on that branch */
5089Sjkh                        trail=nil;
5099Sjkh                        do { if ((date==nil?1:(cmpnum(date,next->date)>=0)) &&
5109Sjkh                                 (author==nil?1:(strcmp(author,next->author)==0)) &&
5119Sjkh                                 (state ==nil?1:(strcmp(state, next->state) ==0))
5129Sjkh                             ) trail = next;
5139Sjkh                             next=next->next;
5149Sjkh                        } while (next!=nil);
5159Sjkh
5169Sjkh                        if (trail==nil) {
5179Sjkh			     cantfindbranch(revno, date, author, state);
5189Sjkh                             return nil;
5199Sjkh                        } else { /* print up to last one suitable */
5209Sjkh                             next = bhead->hsh;
5219Sjkh                             while (next!=trail) {
5229Sjkh				  store1(&store, next);
5239Sjkh                                  next=next->next;
5249Sjkh                             }
5259Sjkh			     store1(&store, next);
5269Sjkh                        }
5279Sjkh			*store = nil;
5289Sjkh                        return next;
5299Sjkh                }
5309Sjkh
5319Sjkh                /* length > field */
5329Sjkh                /* find revision */
5339Sjkh                /* check low */
5349Sjkh                if (cmpnumfld(revno,next->num,field+1)<0) {
5359Sjkh			bufautobegin(&t);
5369Sjkh			error("revision number %s too low", partialno(&t,revno,field+1));
5379Sjkh			bufautoend(&t);
5389Sjkh                        return(nil);
5399Sjkh                }
5409Sjkh		do {
5419Sjkh			store1(&store, next);
5429Sjkh                        trail = next;
5439Sjkh                        next = next->next;
5449Sjkh                } while ((next!=nil) &&
5459Sjkh                       (cmpnumfld(revno,next->num,field+1) >=0));
5469Sjkh
5479Sjkh                if ((length>field+1) &&  /*need exact hit */
5489Sjkh                    (cmpnumfld(revno,trail->num,field+1) !=0)){
5499Sjkh			absent(revno, field+1);
5509Sjkh                        return(nil);
5519Sjkh                }
5529Sjkh                if (length == field+1) {
5539Sjkh                        if ((date!=nil) && (cmpnum(date,trail->date)<0)){
5549Sjkh				error("Revision %s has date %s.",
5559Sjkh					trail->num,
5569Sjkh					date2str(trail->date, datebuf)
5579Sjkh				);
5589Sjkh                                return nil;
5599Sjkh                        }
5609Sjkh                        if ((author!=nil)&&(strcmp(author,trail->author)!=0)) {
5619Sjkh                                error("Revision %s has author %s.",trail->num,trail->author);
5629Sjkh                                return nil;
5639Sjkh                        }
5649Sjkh                        if ((state!=nil)&&(strcmp(state,trail->state)!=0)) {
5659Sjkh                                error("Revision %s has state %s.",trail->num,
5669Sjkh                                       trail->state==nil?"<empty>":trail->state);
5679Sjkh                                return nil;
5689Sjkh                        }
5699Sjkh                }
5709Sjkh                bhead = trail->branches;
5719Sjkh
5729Sjkh	} while ((field+=2) <= length);
5739Sjkh        * store = nil;
5749Sjkh        return trail;
5759Sjkh}
5769Sjkh
5779Sjkh
5789Sjkh	static char const *
5799Sjkhlookupsym(id)
5809Sjkh	char const *id;
5819Sjkh/* Function: looks up id in the list of symbolic names starting
5829Sjkh * with pointer SYMBOLS, and returns a pointer to the corresponding
5839Sjkh * revision number. Returns nil if not present.
5849Sjkh */
5859Sjkh{
5869Sjkh	register struct assoc const *next;
5879Sjkh        next = Symbols;
5889Sjkh        while (next!=nil) {
5899Sjkh                if (strcmp(id, next->symbol)==0)
5909Sjkh			return next->num;
5919Sjkh                else    next=next->nextassoc;
5929Sjkh        }
5939Sjkh        return nil;
5949Sjkh}
5959Sjkh
5969Sjkhint expandsym(source, target)
5979Sjkh	char const *source;
5989Sjkh	struct buf *target;
5999Sjkh/* Function: Source points to a revision number. Expandsym copies
6009Sjkh * the number to target, but replaces all symbolic fields in the
6019Sjkh * source number with their numeric values.
6029Sjkh * Expand a branch followed by `.' to the latest revision on that branch.
6039Sjkh * Ignore `.' after a revision.  Remove leading zeros.
6049Sjkh * returns false on error;
6059Sjkh */
6069Sjkh{
6079Sjkh	return fexpandsym(source, target, (RILE*)0);
6089Sjkh}
6099Sjkh
6109Sjkh	int
6119Sjkhfexpandsym(source, target, fp)
6129Sjkh	char const *source;
6139Sjkh	struct buf *target;
6149Sjkh	RILE *fp;
6159Sjkh/* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM.  */
6169Sjkh{
6179Sjkh	register char const *sp, *bp;
6189Sjkh	register char *tp;
6199Sjkh	char const *tlim;
6209Sjkh        register enum tokens d;
6219Sjkh	unsigned dots;
6229Sjkh
6239Sjkh	sp = source;
6249Sjkh	bufalloc(target, 1);
6259Sjkh	tp = target->string;
6269Sjkh	if (!sp || !*sp) { /*accept nil pointer as a legal value*/
6279Sjkh                *tp='\0';
6289Sjkh                return true;
6299Sjkh        }
6309Sjkh	if (sp[0] == KDELIM  &&  !sp[1]) {
6319Sjkh		if (!getoldkeys(fp))
6329Sjkh			return false;
6339Sjkh		if (!*prevrev.string) {
6349Sjkh			error("working file lacks revision number");
6359Sjkh			return false;
6369Sjkh		}
6379Sjkh		bufscpy(target, prevrev.string);
6389Sjkh		return true;
6399Sjkh	}
6409Sjkh	tlim = tp + target->size;
6419Sjkh	dots = 0;
6429Sjkh
6439Sjkh	for (;;) {
6449Sjkh		switch (ctab[(unsigned char)*sp]) {
6459Sjkh		    case DIGIT:
6469Sjkh			while (*sp=='0' && isdigit(sp[1]))
6479Sjkh                                /* skip leading zeroes */
6489Sjkh                                sp++;
6499Sjkh			do {
6509Sjkh				if (tlim <= tp)
6519Sjkh					tp = bufenlarge(target, &tlim);
6529Sjkh			} while (isdigit(*tp++ = *sp++));
6539Sjkh			--sp;
6549Sjkh			tp[-1] = '\0';
6559Sjkh			break;
6569Sjkh
6579Sjkh		    case LETTER:
6589Sjkh		    case Letter:
6599Sjkh			{
6609Sjkh			register char *p = tp;
6619Sjkh			register size_t s = tp - target->string;
6629Sjkh			do {
6639Sjkh				if (tlim <= p)
6649Sjkh					p = bufenlarge(target, &tlim);
6659Sjkh				*p++ = *sp++;
6669Sjkh			} while ((d=ctab[(unsigned char)*sp])==LETTER ||
6679Sjkh			      d==Letter || d==DIGIT ||
6689Sjkh                              (d==IDCHAR));
6699Sjkh			if (tlim <= p)
6709Sjkh				p = bufenlarge(target, &tlim);
6719Sjkh			*p = 0;
6729Sjkh			tp = target->string + s;
6739Sjkh			}
6749Sjkh			bp = lookupsym(tp);
6759Sjkh                        if (bp==nil) {
6769Sjkh				error("Symbolic number %s is undefined.", tp);
6779Sjkh                                return false;
6789Sjkh                        }
6799Sjkh			do {
6809Sjkh				if (tlim <= tp)
6819Sjkh					tp = bufenlarge(target, &tlim);
6829Sjkh			} while ((*tp++ = *bp++));
6839Sjkh			break;
6849Sjkh
6859Sjkh		    default:
6869Sjkh			goto improper;
6879Sjkh                }
6889Sjkh		switch (*sp++) {
6899Sjkh		    case '\0': return true;
6909Sjkh		    case '.': break;
6919Sjkh		    default: goto improper;
6929Sjkh		}
6939Sjkh		if (!*sp) {
6949Sjkh			if (dots & 1)
6959Sjkh				goto improper;
6969Sjkh			if (!(bp = branchtip(target->string)))
6979Sjkh				return false;
6989Sjkh			bufscpy(target, bp);
6999Sjkh			return true;
7009Sjkh		}
7019Sjkh		++dots;
7029Sjkh		tp[-1] = '.';
7039Sjkh        }
7049Sjkh
7059Sjkh    improper:
7069Sjkh	error("improper revision number: %s", source);
7079Sjkh	return false;
7089Sjkh}
7099Sjkh
7109Sjkh	static char const *
7119Sjkhbranchtip(branch)
7129Sjkh	char const *branch;
7139Sjkh{
7149Sjkh	struct hshentry *h;
7159Sjkh	struct hshentries *hs;
7169Sjkh
7179Sjkh	h  =  genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
7189Sjkh	return h ? h->num : (char const*)0;
7199Sjkh}
7209Sjkh
7219Sjkh	char const *
7229Sjkhtiprev()
7239Sjkh{
7249Sjkh	return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
7259Sjkh}
7269Sjkh
7279Sjkh
7289Sjkh
7299Sjkh#ifdef REVTEST
7309Sjkh
7319Sjkhchar const cmdid[] = "revtest";
7329Sjkh
7339Sjkh	int
7349Sjkhmain(argc,argv)
7359Sjkhint argc; char * argv[];
7369Sjkh{
7379Sjkh	static struct buf numricrevno;
7389Sjkh	char symrevno[100];       /* used for input of revision numbers */
7399Sjkh        char author[20];
7409Sjkh        char state[20];
7419Sjkh        char date[20];
7429Sjkh	struct hshentries *gendeltas;
7439Sjkh        struct hshentry * target;
7449Sjkh        int i;
7459Sjkh
7469Sjkh        if (argc<2) {
7479Sjkh		aputs("No input file\n",stderr);
7489Sjkh		exitmain(EXIT_FAILURE);
7499Sjkh        }
7509Sjkh	if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
7519Sjkh		faterror("can't open input file %s", argv[1]);
7529Sjkh        }
7539Sjkh        Lexinit();
7549Sjkh        getadmin();
7559Sjkh
7569Sjkh        gettree();
7579Sjkh
7589Sjkh        getdesc(false);
7599Sjkh
7609Sjkh        do {
7619Sjkh                /* all output goes to stderr, to have diagnostics and       */
7629Sjkh                /* errors in sequence.                                      */
7639Sjkh		aputs("\nEnter revision number or <return> or '.': ",stderr);
7649Sjkh		if (!gets(symrevno)) break;
7659Sjkh                if (*symrevno == '.') break;
7669Sjkh		aprintf(stderr,"%s;\n",symrevno);
7679Sjkh		expandsym(symrevno,&numricrevno);
7689Sjkh		aprintf(stderr,"expanded number: %s; ",numricrevno.string);
7699Sjkh		aprintf(stderr,"Date: ");
7709Sjkh		gets(date); aprintf(stderr,"%s; ",date);
7719Sjkh		aprintf(stderr,"Author: ");
7729Sjkh		gets(author); aprintf(stderr,"%s; ",author);
7739Sjkh		aprintf(stderr,"State: ");
7749Sjkh		gets(state); aprintf(stderr, "%s;\n", state);
7759Sjkh		target = genrevs(numricrevno.string, *date?date:(char *)nil, *author?author:(char *)nil,
7769Sjkh				 *state?state:(char*)nil, &gendeltas);
7779Sjkh                if (target!=nil) {
7789Sjkh			while (gendeltas) {
7799Sjkh				aprintf(stderr,"%s\n",gendeltas->first->num);
7809Sjkh				gendeltas = gendeltas->next;
7819Sjkh                        }
7829Sjkh                }
7839Sjkh        } while (true);
7849Sjkh	aprintf(stderr,"done\n");
7859Sjkh	exitmain(EXIT_SUCCESS);
7869Sjkh}
7879Sjkh
7889Sjkhexiting void exiterr() { _exit(EXIT_FAILURE); }
7899Sjkh
7909Sjkh#endif
791