111894Speter/* Handle RCS revision numbers.  */
29Sjkh
311894Speter/* Copyright 1982, 1988, 1989 Walter Tichy
411894Speter   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
59Sjkh   Distributed under license by the Free Software Foundation, Inc.
69Sjkh
79SjkhThis file is part of RCS.
89Sjkh
99SjkhRCS is free software; you can redistribute it and/or modify
109Sjkhit under the terms of the GNU General Public License as published by
119Sjkhthe Free Software Foundation; either version 2, or (at your option)
129Sjkhany later version.
139Sjkh
149SjkhRCS is distributed in the hope that it will be useful,
159Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of
169SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
179SjkhGNU General Public License for more details.
189Sjkh
199SjkhYou should have received a copy of the GNU General Public License
2011894Speteralong with RCS; see the file COPYING.
2111894SpeterIf not, write to the Free Software Foundation,
2211894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
239Sjkh
249SjkhReport problems and direct all questions to:
259Sjkh
269Sjkh    rcs-bugs@cs.purdue.edu
279Sjkh
289Sjkh*/
299Sjkh
3011894Speter/*
3111894Speter * Revision 5.10  1995/06/16 06:19:24  eggert
3211894Speter * Update FSF address.
338858Srgrimes *
3411894Speter * Revision 5.9  1995/06/01 16:23:43  eggert
3511894Speter * (cmpdate, normalizeyear): New functions work around MKS RCS incompatibility.
3611894Speter * (cmpnum, compartial): s[d] -> *(s+d) to work around Cray compiler bug.
3711894Speter * (genrevs, genbranch): cmpnum -> cmpdate
3811894Speter *
3911894Speter * Revision 5.8  1994/03/17 14:05:48  eggert
4011894Speter * Remove lint.
4111894Speter *
4211894Speter * Revision 5.7  1993/11/09 17:40:15  eggert
4311894Speter * Fix format string typos.
4411894Speter *
4511894Speter * Revision 5.6  1993/11/03 17:42:27  eggert
4611894Speter * Revision number `.N' now stands for `D.N', where D is the default branch.
4711894Speter * Add -z.  Improve quality of diagnostics.  Add `namedrev' for Name support.
4811894Speter *
4911894Speter * Revision 5.5  1992/07/28  16:12:44  eggert
5011894Speter * Identifiers may now start with a digit.  Avoid `unsigned'.
5111894Speter *
5211894Speter * Revision 5.4  1992/01/06  02:42:34  eggert
5311894Speter * while (E) ; -> while (E) continue;
5411894Speter *
559Sjkh * Revision 5.3  1991/08/19  03:13:55  eggert
569Sjkh * Add `-r$', `-rB.'.  Remove botches like `<now>' from messages.  Tune.
579Sjkh *
589Sjkh * Revision 5.2  1991/04/21  11:58:28  eggert
599Sjkh * Add tiprev().
609Sjkh *
619Sjkh * Revision 5.1  1991/02/25  07:12:43  eggert
629Sjkh * Avoid overflow when comparing revision numbers.
639Sjkh *
649Sjkh * Revision 5.0  1990/08/22  08:13:43  eggert
659Sjkh * Remove compile-time limits; use malloc instead.
669Sjkh * Ansify and Posixate.  Tune.
679Sjkh * Remove possibility of an internal error.  Remove lint.
689Sjkh *
699Sjkh * Revision 4.5  89/05/01  15:13:22  narten
709Sjkh * changed copyright header to reflect current distribution rules
718858Srgrimes *
729Sjkh * Revision 4.4  87/12/18  11:45:22  narten
738858Srgrimes * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
749Sjkh * since there's now a return value there with a value. (Guy Harris)
758858Srgrimes *
769Sjkh * Revision 4.3  87/10/18  10:38:42  narten
778858Srgrimes * Updating version numbers. Changes relative to version 1.1 actually
789Sjkh * relative to 4.1
798858Srgrimes *
809Sjkh * Revision 1.3  87/09/24  14:00:37  narten
818858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
829Sjkh * warnings)
838858Srgrimes *
849Sjkh * Revision 1.2  87/03/27  14:22:37  jenkins
859Sjkh * Port to suns
868858Srgrimes *
879Sjkh * Revision 4.1  83/03/25  21:10:45  wft
889Sjkh * Only changed $Header to $Id.
898858Srgrimes *
909Sjkh * Revision 3.4  82/12/04  13:24:08  wft
919Sjkh * Replaced getdelta() with gettree().
929Sjkh *
939Sjkh * Revision 3.3  82/11/28  21:33:15  wft
949Sjkh * fixed compartial() and compnum() for nil-parameters; fixed nils
959Sjkh * in error messages. Testprogram output shortenend.
969Sjkh *
979Sjkh * Revision 3.2  82/10/18  21:19:47  wft
989Sjkh * renamed compnum->cmpnum, compnumfld->cmpnumfld,
999Sjkh * numericrevno->numricrevno.
1009Sjkh *
1019Sjkh * Revision 3.1  82/10/11  19:46:09  wft
1029Sjkh * changed expandsym() to check for source==nil; returns zero length string
1039Sjkh * in that case.
1049Sjkh */
1059Sjkh
1069Sjkh#include "rcsbase.h"
1079Sjkh
10850472SpeterlibId(revId, "$FreeBSD$")
1099Sjkh
1109Sjkhstatic char const *branchtip P((char const*));
11111894Speterstatic char const *lookupsym P((char const*));
11211894Speterstatic char const *normalizeyear P((char const*,char[5]));
11311894Speterstatic struct hshentry *genbranch P((struct hshentry const*,char const*,int,char const*,char const*,char const*,struct hshentries**));
11411894Speterstatic void absent P((char const*,int));
11511894Speterstatic void cantfindbranch P((char const*,char const[datesize],char const*,char const*));
11611894Speterstatic void store1 P((struct hshentries***,struct hshentry*));
1179Sjkh
1189Sjkh
1199Sjkh
12011894Speter	int
1219Sjkhcountnumflds(s)
1229Sjkh	char const *s;
1239Sjkh/* Given a pointer s to a dotted number (date or revision number),
1249Sjkh * countnumflds returns the number of digitfields in s.
1259Sjkh */
1269Sjkh{
1279Sjkh	register char const *sp;
12811894Speter	register int count;
12911894Speter	if (!(sp=s) || !*sp)
13011894Speter		return 0;
1319Sjkh        count = 1;
1329Sjkh	do {
1339Sjkh                if (*sp++ == '.') count++;
1349Sjkh	} while (*sp);
1359Sjkh        return(count);
1369Sjkh}
1379Sjkh
1389Sjkh	void
1399Sjkhgetbranchno(revno,branchno)
1409Sjkh	char const *revno;
1419Sjkh	struct buf *branchno;
14211894Speter/* Given a revision number revno, getbranchno copies the number of the branch
1439Sjkh * on which revno is into branchno. If revno itself is a branch number,
1449Sjkh * it is copied unchanged.
1459Sjkh */
1469Sjkh{
14711894Speter	register int numflds;
1489Sjkh	register char *tp;
1499Sjkh
1509Sjkh	bufscpy(branchno, revno);
1519Sjkh        numflds=countnumflds(revno);
1529Sjkh	if (!(numflds & 1)) {
1539Sjkh		tp = branchno->string;
1549Sjkh		while (--numflds)
1559Sjkh			while (*tp++ != '.')
15611894Speter				continue;
1579Sjkh                *(tp-1)='\0';
1589Sjkh        }
1599Sjkh}
1609Sjkh
1619Sjkh
1629Sjkh
1639Sjkhint cmpnum(num1, num2)
1649Sjkh	char const *num1, *num2;
1659Sjkh/* compares the two dotted numbers num1 and num2 lexicographically
1669Sjkh * by field. Individual fields are compared numerically.
1679Sjkh * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
1689Sjkh * omitted fields are assumed to be higher than the existing ones.
1699Sjkh*/
1709Sjkh{
1719Sjkh	register char const *s1, *s2;
1729Sjkh	register size_t d1, d2;
1739Sjkh	register int r;
1749Sjkh
17511894Speter	s1 = num1 ? num1 : "";
17611894Speter	s2 = num2 ? num2 : "";
1779Sjkh
1789Sjkh	for (;;) {
1799Sjkh		/* Give precedence to shorter one.  */
1809Sjkh		if (!*s1)
1819Sjkh			return (unsigned char)*s2;
1829Sjkh		if (!*s2)
1839Sjkh			return -1;
1849Sjkh
1859Sjkh		/* Strip leading zeros, then find number of digits.  */
18611894Speter		while (*s1=='0') ++s1;
18711894Speter		while (*s2=='0') ++s2;
18811894Speter		for (d1=0; isdigit(*(s1+d1)); d1++) continue;
18911894Speter		for (d2=0; isdigit(*(s2+d2)); d2++) continue;
1909Sjkh
1919Sjkh		/* Do not convert to integer; it might overflow!  */
1929Sjkh		if (d1 != d2)
1939Sjkh			return d1<d2 ? -1 : 1;
1949Sjkh		if ((r = memcmp(s1, s2, d1)))
1959Sjkh			return r;
1969Sjkh		s1 += d1;
1979Sjkh		s2 += d1;
1989Sjkh
1999Sjkh                /* skip '.' */
2009Sjkh		if (*s1) s1++;
2019Sjkh		if (*s2) s2++;
2029Sjkh	}
2039Sjkh}
2049Sjkh
2059Sjkh
2069Sjkh
2079Sjkhint cmpnumfld(num1, num2, fld)
2089Sjkh	char const *num1, *num2;
20911894Speter	int fld;
2109Sjkh/* Compare the two dotted numbers at field fld.
2119Sjkh * num1 and num2 must have at least fld fields.
2129Sjkh * fld must be positive.
2139Sjkh*/
2149Sjkh{
2159Sjkh	register char const *s1, *s2;
2169Sjkh	register size_t d1, d2;
2179Sjkh
2189Sjkh	s1 = num1;
2199Sjkh	s2 = num2;
2209Sjkh        /* skip fld-1 fields */
2219Sjkh	while (--fld) {
2229Sjkh		while (*s1++ != '.')
22311894Speter			continue;
2249Sjkh		while (*s2++ != '.')
22511894Speter			continue;
2269Sjkh	}
2279Sjkh        /* Now s1 and s2 point to the beginning of the respective fields */
22811894Speter	while (*s1=='0') ++s1;  for (d1=0; isdigit(*(s1+d1)); d1++) continue;
22911894Speter	while (*s2=='0') ++s2;  for (d2=0; isdigit(*(s2+d2)); d2++) continue;
2309Sjkh
2319Sjkh	return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
2329Sjkh}
2339Sjkh
2349Sjkh
23511894Speter	int
23611894Spetercmpdate(d1, d2)
23711894Speter	char const *d1, *d2;
23811894Speter/*
23911894Speter* Compare the two dates.  This is just like cmpnum,
24011894Speter* except that for compatibility with old versions of RCS,
24111894Speter* 1900 is added to dates with two-digit years.
24211894Speter*/
24311894Speter{
24411894Speter	char year1[5], year2[5];
24511894Speter	int r = cmpnumfld(normalizeyear(d1,year1), normalizeyear(d2,year2), 1);
24611894Speter
24711894Speter	if (r)
24811894Speter		return r;
24911894Speter	else {
25011894Speter		while (isdigit(*d1)) d1++;  d1 += *d1=='.';
25111894Speter		while (isdigit(*d2)) d2++;  d2 += *d2=='.';
25211894Speter		return cmpnum(d1, d2);
25311894Speter	}
25411894Speter}
25511894Speter
25611894Speter	static char const *
25711894Speternormalizeyear(date, year)
25811894Speter	char const *date;
25911894Speter	char year[5];
26011894Speter{
26111894Speter	if (isdigit(date[0]) && isdigit(date[1]) && !isdigit(date[2])) {
26211894Speter		year[0] = '1';
26311894Speter		year[1] = '9';
26411894Speter		year[2] = date[0];
26511894Speter		year[3] = date[1];
26611894Speter		year[4] = 0;
26711894Speter		return year;
26811894Speter	} else
26911894Speter		return date;
27011894Speter}
27111894Speter
27211894Speter
2739Sjkh	static void
2749Sjkhcantfindbranch(revno, date, author, state)
2759Sjkh	char const *revno, date[datesize], *author, *state;
2769Sjkh{
27711894Speter	char datebuf[datesize + zonelenmax];
2789Sjkh
27911894Speter	rcserror("No revision on branch %s has%s%s%s%s%s%s.",
2809Sjkh		revno,
2819Sjkh		date ? " a date before " : "",
2829Sjkh		date ? date2str(date,datebuf) : "",
2839Sjkh		author ? " and author "+(date?0:4) : "",
2849Sjkh		author ? author : "",
2859Sjkh		state ? " and state "+(date||author?0:4) : "",
2869Sjkh		state ? state : ""
2879Sjkh	);
2889Sjkh}
2899Sjkh
2909Sjkh	static void
2919Sjkhabsent(revno, field)
2929Sjkh	char const *revno;
29311894Speter	int field;
2949Sjkh{
2959Sjkh	struct buf t;
2969Sjkh	bufautobegin(&t);
29711894Speter	rcserror("%s %s absent", field&1?"revision":"branch",
2989Sjkh		partialno(&t,revno,field)
2999Sjkh	);
3009Sjkh	bufautoend(&t);
3019Sjkh}
3029Sjkh
3039Sjkh
3049Sjkh	int
3059Sjkhcompartial(num1, num2, length)
3069Sjkh	char const *num1, *num2;
30711894Speter	int length;
3089Sjkh
3099Sjkh/*   compare the first "length" fields of two dot numbers;
3109Sjkh     the omitted field is considered to be larger than any number  */
3119Sjkh/*   restriction:  at least one number has length or more fields   */
3129Sjkh
3139Sjkh{
3149Sjkh	register char const *s1, *s2;
3159Sjkh	register size_t d1, d2;
3169Sjkh	register int r;
3179Sjkh
3189Sjkh        s1 = num1;      s2 = num2;
3199Sjkh	if (!s1) return 1;
3209Sjkh	if (!s2) return -1;
3219Sjkh
3229Sjkh	for (;;) {
3239Sjkh	    if (!*s1) return 1;
3249Sjkh	    if (!*s2) return -1;
3259Sjkh
32611894Speter	    while (*s1=='0') ++s1; for (d1=0; isdigit(*(s1+d1)); d1++) continue;
32711894Speter	    while (*s2=='0') ++s2; for (d2=0; isdigit(*(s2+d2)); d2++) continue;
3289Sjkh
3299Sjkh	    if (d1 != d2)
3309Sjkh		    return d1<d2 ? -1 : 1;
3319Sjkh	    if ((r = memcmp(s1, s2, d1)))
3329Sjkh		    return r;
33311894Speter	    if (!--length)
33411894Speter		    return 0;
33511894Speter
3369Sjkh	    s1 += d1;
3379Sjkh	    s2 += d1;
3389Sjkh
3399Sjkh	    if (*s1 == '.') s1++;
3409Sjkh            if (*s2 == '.') s2++;
3419Sjkh	}
3429Sjkh}
3439Sjkh
3449Sjkh
3459Sjkhchar * partialno(rev1,rev2,length)
3469Sjkh	struct buf *rev1;
3479Sjkh	char const *rev2;
34811894Speter	register int length;
3499Sjkh/* Function: Copies length fields of revision number rev2 into rev1.
3509Sjkh * Return rev1's string.
3519Sjkh */
3529Sjkh{
3539Sjkh	register char *r1;
3549Sjkh
3559Sjkh	bufscpy(rev1, rev2);
3569Sjkh	r1 = rev1->string;
3579Sjkh        while (length) {
3589Sjkh		while (*r1!='.' && *r1)
3599Sjkh			++r1;
3609Sjkh		++r1;
3619Sjkh                length--;
3629Sjkh        }
3639Sjkh        /* eliminate last '.'*/
3649Sjkh        *(r1-1)='\0';
3659Sjkh	return rev1->string;
3669Sjkh}
3679Sjkh
3689Sjkh
3699Sjkh
3709Sjkh
3719Sjkh	static void
3729Sjkhstore1(store, next)
3739Sjkh	struct hshentries ***store;
3749Sjkh	struct hshentry *next;
3759Sjkh/*
3769Sjkh * Allocate a new list node that addresses NEXT.
3779Sjkh * Append it to the list that **STORE is the end pointer of.
3789Sjkh */
3799Sjkh{
3809Sjkh	register struct hshentries *p;
3819Sjkh
3829Sjkh	p = ftalloc(struct hshentries);
3839Sjkh	p->first = next;
3849Sjkh	**store = p;
3859Sjkh	*store = &p->rest;
3869Sjkh}
3879Sjkh
3889Sjkhstruct hshentry * genrevs(revno,date,author,state,store)
3899Sjkh	char const *revno, *date, *author, *state;
3909Sjkh	struct hshentries **store;
3919Sjkh/* Function: finds the deltas needed for reconstructing the
3929Sjkh * revision given by revno, date, author, and state, and stores pointers
3939Sjkh * to these deltas into a list whose starting address is given by store.
3949Sjkh * The last delta (target delta) is returned.
39511894Speter * If the proper delta could not be found, 0 is returned.
3969Sjkh */
3979Sjkh{
39811894Speter	int length;
3999Sjkh        register struct hshentry * next;
4009Sjkh        int result;
4019Sjkh	char const *branchnum;
4029Sjkh	struct buf t;
40311894Speter	char datebuf[datesize + zonelenmax];
4049Sjkh
4059Sjkh	bufautobegin(&t);
4069Sjkh
4079Sjkh	if (!(next = Head)) {
40811894Speter		rcserror("RCS file empty");
4099Sjkh		goto norev;
4109Sjkh        }
4119Sjkh
4129Sjkh        length = countnumflds(revno);
4139Sjkh
4149Sjkh        if (length >= 1) {
4159Sjkh                /* at least one field; find branch exactly */
4169Sjkh		while ((result=cmpnumfld(revno,next->num,1)) < 0) {
4179Sjkh			store1(&store, next);
4189Sjkh                        next = next->next;
4199Sjkh			if (!next) {
42011894Speter			    rcserror("branch number %s too low", partialno(&t,revno,1));
4219Sjkh			    goto norev;
4229Sjkh			}
4239Sjkh                }
4249Sjkh
4259Sjkh		if (result>0) {
4269Sjkh			absent(revno, 1);
4279Sjkh			goto norev;
4289Sjkh		}
4299Sjkh        }
4309Sjkh        if (length<=1){
4319Sjkh                /* pick latest one on given branch */
4329Sjkh                branchnum = next->num; /* works even for empty revno*/
43311894Speter		while (next &&
43411894Speter		       cmpnumfld(branchnum,next->num,1) == 0 &&
43511894Speter		       (
43611894Speter			(date && cmpdate(date,next->date) < 0) ||
43711894Speter			(author && strcmp(author,next->author) != 0) ||
43811894Speter			(state && strcmp(state,next->state) != 0)
43911894Speter		       )
44011894Speter		      )
4419Sjkh		{
4429Sjkh			store1(&store, next);
4439Sjkh                        next=next->next;
4449Sjkh                }
44511894Speter		if (!next ||
4469Sjkh                    (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
4479Sjkh			cantfindbranch(
4489Sjkh				length ? revno : partialno(&t,branchnum,1),
4499Sjkh				date, author, state
4509Sjkh			);
4519Sjkh			goto norev;
4529Sjkh                } else {
4539Sjkh			store1(&store, next);
4549Sjkh                }
45511894Speter		*store = 0;
4569Sjkh                return next;
4579Sjkh        }
4589Sjkh
4599Sjkh        /* length >=2 */
4609Sjkh        /* find revision; may go low if length==2*/
4619Sjkh	while ((result=cmpnumfld(revno,next->num,2)) < 0  &&
4629Sjkh               (cmpnumfld(revno,next->num,1)==0) ) {
4639Sjkh		store1(&store, next);
4649Sjkh                next = next->next;
4659Sjkh		if (!next)
4669Sjkh			break;
4679Sjkh        }
4689Sjkh
46911894Speter	if (!next || cmpnumfld(revno,next->num,1) != 0) {
47011894Speter		rcserror("revision number %s too low", partialno(&t,revno,2));
4719Sjkh		goto norev;
4729Sjkh        }
4739Sjkh        if ((length>2) && (result!=0)) {
4749Sjkh		absent(revno, 2);
4759Sjkh		goto norev;
4769Sjkh        }
4779Sjkh
4789Sjkh        /* print last one */
4799Sjkh	store1(&store, next);
4809Sjkh
4819Sjkh        if (length>2)
4829Sjkh                return genbranch(next,revno,length,date,author,state,store);
4839Sjkh        else { /* length == 2*/
48411894Speter		if (date && cmpdate(date,next->date)<0) {
48511894Speter			rcserror("Revision %s has date %s.",
4869Sjkh				next->num,
4879Sjkh				date2str(next->date, datebuf)
4889Sjkh			);
48911894Speter			return 0;
49011894Speter		}
49111894Speter		if (author && strcmp(author,next->author)!=0) {
49211894Speter			rcserror("Revision %s has author %s.",
49311894Speter				next->num, next->author
49411894Speter			);
49511894Speter			return 0;
4969Sjkh                }
49711894Speter		if (state && strcmp(state,next->state)!=0) {
49811894Speter			rcserror("Revision %s has state %s.",
49911894Speter				next->num,
50011894Speter				next->state ? next->state : "<empty>"
50111894Speter			);
50211894Speter			return 0;
5039Sjkh                }
50411894Speter		*store = 0;
5059Sjkh                return next;
5069Sjkh        }
5079Sjkh
5089Sjkh    norev:
5099Sjkh	bufautoend(&t);
51011894Speter	return 0;
5119Sjkh}
5129Sjkh
5139Sjkh
5149Sjkh
5159Sjkh
5169Sjkh	static struct hshentry *
5179Sjkhgenbranch(bpoint, revno, length, date, author, state, store)
5189Sjkh	struct hshentry const *bpoint;
5199Sjkh	char const *revno;
52011894Speter	int length;
5219Sjkh	char const *date, *author, *state;
5229Sjkh	struct hshentries **store;
5239Sjkh/* Function: given a branchpoint, a revision number, date, author, and state,
5249Sjkh * genbranch finds the deltas necessary to reconstruct the given revision
5259Sjkh * from the branch point on.
5269Sjkh * Pointers to the found deltas are stored in a list beginning with store.
5279Sjkh * revno must be on a side branch.
52811894Speter * Return 0 on error.
5299Sjkh */
5309Sjkh{
53111894Speter	int field;
5329Sjkh        register struct hshentry * next, * trail;
5339Sjkh	register struct branchhead const *bhead;
5349Sjkh        int result;
5359Sjkh	struct buf t;
53611894Speter	char datebuf[datesize + zonelenmax];
5379Sjkh
5389Sjkh	field = 3;
5399Sjkh        bhead = bpoint->branches;
5409Sjkh
5419Sjkh	do {
5429Sjkh		if (!bhead) {
5439Sjkh			bufautobegin(&t);
54411894Speter			rcserror("no side branches present for %s",
54511894Speter				partialno(&t,revno,field-1)
54611894Speter			);
5479Sjkh			bufautoend(&t);
54811894Speter			return 0;
5499Sjkh		}
5509Sjkh
5519Sjkh                /*find branch head*/
5529Sjkh                /*branches are arranged in increasing order*/
5539Sjkh		while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
5549Sjkh                        bhead = bhead->nextbranch;
5559Sjkh			if (!bhead) {
5569Sjkh			    bufautobegin(&t);
55711894Speter			    rcserror("branch number %s too high",
55811894Speter				partialno(&t,revno,field)
55911894Speter			    );
5609Sjkh			    bufautoend(&t);
56111894Speter			    return 0;
5629Sjkh			}
5639Sjkh                }
5649Sjkh
5659Sjkh		if (result<0) {
5669Sjkh		    absent(revno, field);
56711894Speter		    return 0;
5689Sjkh		}
5699Sjkh
5709Sjkh                next = bhead->hsh;
5719Sjkh                if (length==field) {
5729Sjkh                        /* pick latest one on that branch */
57311894Speter			trail = 0;
57411894Speter			do { if ((!date || cmpdate(date,next->date)>=0) &&
57511894Speter				 (!author || strcmp(author,next->author)==0) &&
57611894Speter				 (!state || strcmp(state,next->state)==0)
5779Sjkh                             ) trail = next;
5789Sjkh                             next=next->next;
57911894Speter			} while (next);
5809Sjkh
58111894Speter			if (!trail) {
5829Sjkh			     cantfindbranch(revno, date, author, state);
58311894Speter			     return 0;
5849Sjkh                        } else { /* print up to last one suitable */
5859Sjkh                             next = bhead->hsh;
5869Sjkh                             while (next!=trail) {
5879Sjkh				  store1(&store, next);
5889Sjkh                                  next=next->next;
5899Sjkh                             }
5909Sjkh			     store1(&store, next);
5919Sjkh                        }
59211894Speter			*store = 0;
5939Sjkh                        return next;
5949Sjkh                }
5959Sjkh
5969Sjkh                /* length > field */
5979Sjkh                /* find revision */
5989Sjkh                /* check low */
5999Sjkh                if (cmpnumfld(revno,next->num,field+1)<0) {
6009Sjkh			bufautobegin(&t);
60111894Speter			rcserror("revision number %s too low",
60211894Speter				partialno(&t,revno,field+1)
60311894Speter			);
6049Sjkh			bufautoend(&t);
60511894Speter			return 0;
6069Sjkh                }
6079Sjkh		do {
6089Sjkh			store1(&store, next);
6099Sjkh                        trail = next;
6109Sjkh                        next = next->next;
61111894Speter		} while (next && cmpnumfld(revno,next->num,field+1)>=0);
6129Sjkh
6139Sjkh                if ((length>field+1) &&  /*need exact hit */
6149Sjkh                    (cmpnumfld(revno,trail->num,field+1) !=0)){
6159Sjkh			absent(revno, field+1);
61611894Speter			return 0;
6179Sjkh                }
6189Sjkh                if (length == field+1) {
61911894Speter			if (date && cmpdate(date,trail->date)<0) {
62011894Speter				rcserror("Revision %s has date %s.",
6219Sjkh					trail->num,
6229Sjkh					date2str(trail->date, datebuf)
6239Sjkh				);
62411894Speter				return 0;
6259Sjkh                        }
62611894Speter			if (author && strcmp(author,trail->author)!=0) {
62711894Speter				rcserror("Revision %s has author %s.",
62811894Speter					trail->num, trail->author
62911894Speter				);
63011894Speter				return 0;
6319Sjkh                        }
63211894Speter			if (state && strcmp(state,trail->state)!=0) {
63311894Speter				rcserror("Revision %s has state %s.",
63411894Speter					trail->num,
63511894Speter					trail->state ? trail->state : "<empty>"
63611894Speter				);
63711894Speter				return 0;
6389Sjkh                        }
6399Sjkh                }
6409Sjkh                bhead = trail->branches;
6419Sjkh
6429Sjkh	} while ((field+=2) <= length);
64311894Speter	*store = 0;
6449Sjkh        return trail;
6459Sjkh}
6469Sjkh
6479Sjkh
6489Sjkh	static char const *
6499Sjkhlookupsym(id)
6509Sjkh	char const *id;
6519Sjkh/* Function: looks up id in the list of symbolic names starting
6529Sjkh * with pointer SYMBOLS, and returns a pointer to the corresponding
65311894Speter * revision number.  Return 0 if not present.
6549Sjkh */
6559Sjkh{
6569Sjkh	register struct assoc const *next;
65711894Speter	for (next = Symbols;  next;  next = next->nextassoc)
6589Sjkh                if (strcmp(id, next->symbol)==0)
6599Sjkh			return next->num;
66011894Speter	return 0;
6619Sjkh}
6629Sjkh
6639Sjkhint expandsym(source, target)
6649Sjkh	char const *source;
6659Sjkh	struct buf *target;
6669Sjkh/* Function: Source points to a revision number. Expandsym copies
6679Sjkh * the number to target, but replaces all symbolic fields in the
6689Sjkh * source number with their numeric values.
6699Sjkh * Expand a branch followed by `.' to the latest revision on that branch.
6709Sjkh * Ignore `.' after a revision.  Remove leading zeros.
6719Sjkh * returns false on error;
6729Sjkh */
6739Sjkh{
6749Sjkh	return fexpandsym(source, target, (RILE*)0);
6759Sjkh}
6769Sjkh
6779Sjkh	int
6789Sjkhfexpandsym(source, target, fp)
6799Sjkh	char const *source;
6809Sjkh	struct buf *target;
6819Sjkh	RILE *fp;
6829Sjkh/* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM.  */
6839Sjkh{
6849Sjkh	register char const *sp, *bp;
6859Sjkh	register char *tp;
6869Sjkh	char const *tlim;
68711894Speter	int dots;
6889Sjkh
6899Sjkh	sp = source;
6909Sjkh	bufalloc(target, 1);
6919Sjkh	tp = target->string;
69211894Speter	if (!sp || !*sp) { /* Accept 0 pointer as a legal value.  */
6939Sjkh                *tp='\0';
6949Sjkh                return true;
6959Sjkh        }
6969Sjkh	if (sp[0] == KDELIM  &&  !sp[1]) {
6979Sjkh		if (!getoldkeys(fp))
6989Sjkh			return false;
6999Sjkh		if (!*prevrev.string) {
70011894Speter			workerror("working file lacks revision number");
7019Sjkh			return false;
7029Sjkh		}
7039Sjkh		bufscpy(target, prevrev.string);
7049Sjkh		return true;
7059Sjkh	}
7069Sjkh	tlim = tp + target->size;
7079Sjkh	dots = 0;
7089Sjkh
7099Sjkh	for (;;) {
71011894Speter		register char *p = tp;
71111894Speter		size_t s = tp - target->string;
71211894Speter		int id = false;
71311894Speter		for (;;) {
71411894Speter		    switch (ctab[(unsigned char)*sp]) {
71511894Speter			case IDCHAR:
71611894Speter			case LETTER:
71711894Speter			case Letter:
71811894Speter			    id = true;
71911894Speter			    /* fall into */
72011894Speter			case DIGIT:
72111894Speter			    if (tlim <= p)
72211894Speter				    p = bufenlarge(target, &tlim);
72311894Speter			    *p++ = *sp++;
72411894Speter			    continue;
7259Sjkh
72611894Speter			default:
72711894Speter			    break;
72811894Speter		    }
72911894Speter		    break;
73011894Speter		}
73111894Speter		if (tlim <= p)
73211894Speter			p = bufenlarge(target, &tlim);
73311894Speter		*p = 0;
73411894Speter		tp = target->string + s;
73511894Speter
73611894Speter		if (id) {
7379Sjkh			bp = lookupsym(tp);
73811894Speter			if (!bp) {
73911894Speter				rcserror("Symbolic name `%s' is undefined.",tp);
7409Sjkh                                return false;
7419Sjkh                        }
74211894Speter		} else {
74311894Speter			/* skip leading zeros */
74411894Speter			for (bp = tp;  *bp=='0' && isdigit(bp[1]);  bp++)
74511894Speter				continue;
7469Sjkh
74711894Speter			if (!*bp)
74811894Speter			    if (s || *sp!='.')
74911894Speter				break;
75011894Speter			    else {
75111894Speter				/* Insert default branch before initial `.'.  */
75211894Speter				char const *b;
75311894Speter				if (Dbranch)
75411894Speter				    b = Dbranch;
75511894Speter				else if (Head)
75611894Speter				    b = Head->num;
75711894Speter				else
75811894Speter				    break;
75911894Speter				getbranchno(b, target);
76011894Speter				bp = tp = target->string;
76111894Speter				tlim = tp + target->size;
76211894Speter			    }
76311894Speter		}
76411894Speter
76511894Speter		while ((*tp++ = *bp++))
76611894Speter			if (tlim <= tp)
76711894Speter				tp = bufenlarge(target, &tlim);
76811894Speter
7699Sjkh		switch (*sp++) {
77011894Speter		    case '\0':
7719Sjkh			return true;
77211894Speter
77311894Speter		    case '.':
77411894Speter			if (!*sp) {
77511894Speter				if (dots & 1)
77611894Speter					break;
77711894Speter				if (!(bp = branchtip(target->string)))
77811894Speter					return false;
77911894Speter				bufscpy(target, bp);
78011894Speter				return true;
78111894Speter			}
78211894Speter			++dots;
78311894Speter			tp[-1] = '.';
78411894Speter			continue;
7859Sjkh		}
78611894Speter		break;
7879Sjkh        }
7889Sjkh
78911894Speter	rcserror("improper revision number: %s", source);
7909Sjkh	return false;
7919Sjkh}
7929Sjkh
79311894Speter	char const *
79411894Speternamedrev(name, delta)
79511894Speter	char const *name;
79611894Speter	struct hshentry *delta;
79711894Speter/* Yield NAME if it names DELTA, 0 otherwise.  */
79811894Speter{
79911894Speter	if (name) {
80011894Speter		char const *id = 0, *p, *val;
80111894Speter		for (p = name;  ;  p++)
80211894Speter			switch (ctab[(unsigned char)*p]) {
80311894Speter				case IDCHAR:
80411894Speter				case LETTER:
80511894Speter				case Letter:
80611894Speter					id = name;
80711894Speter					break;
80811894Speter
80911894Speter				case DIGIT:
81011894Speter					break;
81111894Speter
81211894Speter				case UNKN:
81311894Speter					if (!*p && id &&
81411894Speter						(val = lookupsym(id)) &&
81511894Speter						strcmp(val, delta->num) == 0
81611894Speter					)
81711894Speter						return id;
81811894Speter					/* fall into */
81911894Speter				default:
82011894Speter					return 0;
82111894Speter			}
82211894Speter	}
82311894Speter	return 0;
82411894Speter}
82511894Speter
8269Sjkh	static char const *
8279Sjkhbranchtip(branch)
8289Sjkh	char const *branch;
8299Sjkh{
8309Sjkh	struct hshentry *h;
8319Sjkh	struct hshentries *hs;
8329Sjkh
8339Sjkh	h  =  genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
8349Sjkh	return h ? h->num : (char const*)0;
8359Sjkh}
8369Sjkh
8379Sjkh	char const *
8389Sjkhtiprev()
8399Sjkh{
8409Sjkh	return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
8419Sjkh}
8429Sjkh
8439Sjkh
8449Sjkh
8459Sjkh#ifdef REVTEST
8469Sjkh
84711894Speter/*
84811894Speter* Test the routines that generate a sequence of delta numbers
84911894Speter* needed to regenerate a given delta.
85011894Speter*/
85111894Speter
8529Sjkhchar const cmdid[] = "revtest";
8539Sjkh
8549Sjkh	int
8559Sjkhmain(argc,argv)
8569Sjkhint argc; char * argv[];
8579Sjkh{
8589Sjkh	static struct buf numricrevno;
8599Sjkh	char symrevno[100];       /* used for input of revision numbers */
8609Sjkh        char author[20];
8619Sjkh        char state[20];
8629Sjkh        char date[20];
8639Sjkh	struct hshentries *gendeltas;
8649Sjkh        struct hshentry * target;
8659Sjkh        int i;
8669Sjkh
8679Sjkh        if (argc<2) {
8689Sjkh		aputs("No input file\n",stderr);
8699Sjkh		exitmain(EXIT_FAILURE);
8709Sjkh        }
8719Sjkh	if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
8729Sjkh		faterror("can't open input file %s", argv[1]);
8739Sjkh        }
8749Sjkh        Lexinit();
8759Sjkh        getadmin();
8769Sjkh
8779Sjkh        gettree();
8789Sjkh
8799Sjkh        getdesc(false);
8809Sjkh
8819Sjkh        do {
8829Sjkh                /* all output goes to stderr, to have diagnostics and       */
8839Sjkh                /* errors in sequence.                                      */
8849Sjkh		aputs("\nEnter revision number or <return> or '.': ",stderr);
88512553Sjkh		if (!fgets(symrevno, 100, stdin)) break;
8869Sjkh                if (*symrevno == '.') break;
8879Sjkh		aprintf(stderr,"%s;\n",symrevno);
8889Sjkh		expandsym(symrevno,&numricrevno);
8899Sjkh		aprintf(stderr,"expanded number: %s; ",numricrevno.string);
8909Sjkh		aprintf(stderr,"Date: ");
89112553Sjkh		fgets(date, 20, stdin); aprintf(stderr,"%s; ",date);
8929Sjkh		aprintf(stderr,"Author: ");
89312553Sjkh		fgets(author, 20, stdin); aprintf(stderr,"%s; ",author);
8949Sjkh		aprintf(stderr,"State: ");
89512553Sjkh		fgets(state, 20, stdin); aprintf(stderr, "%s;\n", state);
89611894Speter		target = genrevs(numricrevno.string, *date?date:(char *)0, *author?author:(char *)0,
89711894Speter				 *state?state:(char*)0, &gendeltas);
89811894Speter		if (target) {
8999Sjkh			while (gendeltas) {
9009Sjkh				aprintf(stderr,"%s\n",gendeltas->first->num);
9019Sjkh				gendeltas = gendeltas->next;
9029Sjkh                        }
9039Sjkh                }
9049Sjkh        } while (true);
9059Sjkh	aprintf(stderr,"done\n");
9069Sjkh	exitmain(EXIT_SUCCESS);
9079Sjkh}
9089Sjkh
90911894Spetervoid exiterr() { _exit(EXIT_FAILURE); }
9109Sjkh
9119Sjkh#endif
912