/*
 *                       RLOG    operation
 */
/*****************************************************************************
 *                       print contents of RCS files
 *****************************************************************************
 *
 * Copyright (C) 1982 by Walter Tichy
 *                       Purdue University
 *                       Computer Science Department
 *                       West Lafayette, IN 47907
 *
 * All rights reserved. No part of this software may be sold or distributed
 * in any form or by any means without the prior written permission of the
 * author.
 * Report problems and direct all inquiries to Tichy@purdue (ARPA net).
 */


/* $Log:	C.rlog $
 * Revision 1.1  90/05/22  21:53:54  pmoore
 * Initial revision
 * 
 * Revision 4.5  87/12/18  11:46:38  narten
 * more lint cleanups (Guy Harris)
 *
 * Revision 4.4  87/10/18  10:41:12  narten
 * Updating version numbers
 * Changes relative to 1.1 actually relative to 4.2
 *
 * Revision 1.3  87/09/24  14:01:10  narten
 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
 * warnings)
 *
 * Revision 1.2  87/03/27  14:22:45  jenkins
 * Port to suns
 *
 * Revision 1.1  84/01/23  14:50:45  kcs
 * Initial revision
 *
 * Revision 4.2  83/12/05  09:18:09  wft
 * changed rewriteflag to external.
 *
 * Revision 4.1  83/05/11  16:16:55  wft
 * Added -b, updated getnumericrev() accordingly.
 * Replaced getpwuid() with getcaller().
 *
 * Revision 3.7  83/05/11  14:24:13  wft
 * Added options -L and -R;
 * Fixed selection bug with -l on multiple files.
 * Fixed error on dates of the form -d'>date' (rewrote getdatepair()).
 *
 * Revision 3.6  82/12/24  15:57:53  wft
 * shortened output format.
 *
 * Revision 3.5  82/12/08  21:45:26  wft
 * removed call to checkaccesslist(); used DATEFORM to format all dates;
 * removed unused variables.
 *
 * Revision 3.4  82/12/04  13:26:25  wft
 * Replaced getdelta() with gettree(); removed updating of field lockedby.
 *
 * Revision 3.3  82/12/03  14:08:20  wft
 * Replaced getlogin with getpwuid(), %02d with %.2d, fancydate with PRINTDATE.
 * Fixed printing of nil, removed printing of Suffix,
 * added shortcut if no revisions are printed, disambiguated struct members.
 *
 * Revision 3.2  82/10/18  21:09:06  wft
 * call to curdir replaced with getfullRCSname(),
 * fixed call to getlogin(), cosmetic changes on output,
 * changed conflicting long identifiers.
 *
 * Revision 3.1  82/10/13  16:07:56  wft
 * fixed type of variables receiving from getc() (char -> int).
 */

#include "rcsbase.h"
#include "time.h"

/* Option processing */

#define OPTIONS "?bd:hLl::Rr:s:tw:"

#define USAGE \
"Usage: rlog [options] file...\n" \
"\n" \
"Options:\n" \
"    -b         Select revisions on the default branch\n" \
"    -d dates   Select revisions within the specified date ranges\n" \
"    -h         Only print header information (no revisions or description)\n" \
"    -L         Ignore RCS files with no locks set\n" \
"    -l [names] Select revisions locked by someone on the list\n" \
"    -R         Only print the RCS filename\n" \
"    -r revs    Only print information on revisions <revs>\n" \
"    -s states  Select revisions with a state in <states>\n" \
"    -t         Print header information (see -h), plus description\n" \
"    -w names   Select revisions checked in by users in <names>\n" \
"\n"

extern int nextc;		/* next input character                  */
extern char *Klog;
extern char *Ktext;
extern FILE *finptr;		/* RCS input file                       */
extern FILE *frewrite;		/* new RCS file                         */
extern int rewriteflag;		/* indicates whether input should be    */
 /* echoed to frewrite */
extern int nerror;		/* error counter                        */

char *RCSfilename, *workfilename, *basename;

char *login_name;			/* caller's login;              */
int descflag, selectflag, selectop;	/* option to print access list,
					 * symbolic names, descriptive text,
					 * locks and Head
					 */
int onlylockflag;		/* option to print only files with locks */
int onlyRCSflag;		/* option to print only RCS file name    */
int lockflag;			/* whether locker option is set          */
int revno;			/* number of revision chosen             */

struct lockers
{				/* lockers in locker option; stored   */
	char *login;		/* lockerlist                         */
	struct lockers *lockerlink;
};

struct stateattri
{				/* states in state option; stored in  */
	char *status;		/* statelist                          */
	struct stateattri *nextstate;
};

struct authors
{				/* login names in author option;      */
	char *login;		/* stored in authorlist               */
	struct authors *nextauthor;
};

struct Revpairs
{				/* revision or branch range in -r     */
	int numfld;		/* option; stored in revlist          */
	char *strtrev;
	char *endrev;
	struct Revpairs *rnext;
};

struct Datepairs
{				/* date range in -d option; stored in */
	char strtdate[datelength];	/* duelst and datelist      */
	char enddate[datelength];
	struct Datepairs *dnext;
};

char Dotstring[200];		/* string of numeric revision name    */
char *Nextdotstring;		/* next available place of Dotstring  */
struct Datepairs *datelist, *duelst;
struct Revpairs *revlist, *Revlst;
int branchflag;			/* set on -b */
struct lockers *lockerlist;
struct stateattri *statelist;
struct authors *authorlist;

/* Forward declarations */
extern void putrunk(void);
extern void putree(struct hshentry *);
extern void putforest(struct branchhead *);
extern void putabranch(struct hshentry *);
extern void putadelta(struct hshentry *, struct hshentry *, int);
extern int readdeltalog(void);
extern void getscript(struct hshentry *);
extern void exttree(struct hshentry *);
extern void getlocker(char *);
extern void getauthor(char *);
extern void getstate(char *);
extern void trunclocks(void);
extern void recentdate(struct hshentry *, struct Datepairs *);
extern void extdate(struct hshentry *);
extern void extractdelta(struct hshentry *);
extern char *procdate(char *target, char *);
extern void getdatepair(char *);
extern void getnumericrev(void);
extern int checkrevpair(char *, char *);
extern void getrevpairs(char *);
extern void choptail(char *);

/* Main procedure */

int main(int argc, char *argv[])
{
	struct Datepairs *currdate;
	struct assoc *curassoc;
	struct access *curaccess;
	struct lock *currlock;
	int ch;

	cmdid = "rlog";
	descflag = selectflag = true;
	lockflag = onlylockflag = selectop = false;
	onlyRCSflag = false;
	lockerlist = nil;
	authorlist = nil;
	statelist = nil;
	Revlst = revlist = nil;
	branchflag = false;
	duelst = datelist = nil;
	login_name = getcaller();

	while ((ch = getopt(argc, argv, OPTIONS)) != EOF)
	{
		switch (ch)
		{
		case 'b':
			selectop = true;
			branchflag = true;
			break;

		case 'd':
			selectop = true;
			getdatepair(optarg);
			break;

		case 'h':
			if (!selectflag)
				warn("option -t overrides -h");
			else
				descflag = false;
			break;

		case 'L':
			onlylockflag = true;
			break;

		case 'l':
			selectop = true;
			lockflag = true;
			getlocker(optarg);
			break;

		case 'R':
			onlyRCSflag = true;
			break;

		case 'r':
			selectop = true;
			getrevpairs(optarg);
			break;

		case 's':
			selectop = true;
			getstate(optarg);
			break;

		case 't':
			selectflag = false;
			if (!descflag)
				warn("option -t overrides -h");
			descflag = true;
			break;

		case 'w':
			selectop = true;
			getauthor(optarg);
			break;

		case '?':
		default:
			fputs(USAGE, stderr);
			exit (ch != '?');
		}
	}

	argv += optind;
	argc -= optind;

	if (argc < 1)
		faterror("No input file\n%s", USAGE);

	/* Expand the remaining elements of argv as wildcarded file names */
	wildcard_expand (&argc, &argv);

	/* now handle all filenames */
	do
	{
		rewriteflag = false;
		finptr = frewrite = nil;

		if (!pairfilenames(argc, argv, true, false))
			continue;

		/*
		 * now RCSfilename contains the name of the RCS file, and
		 * finptr the file descriptor. Workfilename contains the name
		 * of the working file.
		 */

		if (!trysema(RCSfilename, false))
			goto loopend;	/* give up */

		/* do nothing if -L is given and there are no locks */
		if (onlylockflag && Locks == nil)
			goto loopend;

		if (onlyRCSflag)
		{
			VOID fprintf(stdout, "%s\n", RCSfilename);
			goto loopend;
		}

		/*
		 * print RCS filename , working filename and optional
		 * administrative information
		 */
		VOID fprintf(stdout, "\nRCS file:        %s;   ", RCSfilename);
		/* could use getfullRCSname() here, but that is very slow */
		VOID fprintf(stdout, "Working file:    %s\n", workfilename);
		VOID fprintf(stdout, "head:            %s\n", Head == nil ? "" : Head->num);
		VOID fprintf(stdout, "branch:          %s\n", Dbranch == nil ? "" : Dbranch->num);

		VOID fputs("locks:         ", stdout);	/* print locker list   */
		currlock = Locks;
		while (currlock)
		{
			VOID fprintf(stdout, "  %s: %s;", currlock->login,
				         currlock->delta->num);
			currlock = currlock->nextlock;
		}
		if (StrictLocks)
			VOID fputs(Locks == nil ? "  ;  strict" : "  strict", stdout);

		VOID fputs("\naccess list:   ", stdout);	/* print access list  */
		curaccess = AccessList;
		while (curaccess)
		{
			VOID fputs("  ", stdout);
			VOID fputs(curaccess->login, stdout);
			curaccess = curaccess->nextaccess;
		}

		VOID fputs("\nsymbolic names:", stdout);	/* print symbolic names   */
		curassoc = Symbols;
		while (curassoc)
		{
			VOID fprintf(stdout, "  %s: %s;", curassoc->symbol,
				         curassoc->delta->num);
			curassoc = curassoc->nextassoc;
		}

		VOID fprintf(stdout, "\ncomment leader:  \"%s\"\n", Comment);

		gettree();
		VOID fprintf(stdout, "total revisions: %d;    ", TotalDeltas);
		if (Head == nil || !selectflag || !descflag)
		{
			VOID putc('\n', stdout);
			if (descflag)
				VOID fputs("description:\n", stdout);
			getdesc(descflag);
			VOID fputs("=============================================================================\n", stdout);
			goto loopend;
		}


		/* keep only those locks given by -l */
		if (lockflag)
			trunclocks();
		getnumericrev();/* get numeric revision or branch names */
		revno = 0;

		exttree(Head);

		/* get most recently date of the dates pointed by duelst  */
		currdate = duelst;
		while (currdate)
		{
			recentdate(Head, currdate);
			currdate = currdate->dnext;
		}

		extdate(Head);

		/* reinitialize the date specification list   */
		currdate = duelst;
		while (currdate)
		{
			VOID sprintf(currdate->strtdate, DATEFORM, 0, 0, 0, 0, 0, 0);
			currdate = currdate->dnext;
		}

		if (selectop || (selectflag && descflag))
			VOID fprintf(stdout, "selected revisions: %d", revno);
		VOID putc('\n', stdout);
		if (descflag)
			VOID fputs("description:\n", stdout);
		getdesc(descflag);
		while ((nexttok != EOFILE) && readdeltalog());
		if (selectflag && descflag && revno)
		{
			putrunk();
			putree(Head);
			if (nextlex(), nexttok != EOFILE)
				fatserror("syntax error; expecting EOF");
		}
		VOID fputs("=============================================================================\n", stdout);

loopend:
		VOID fclose(finptr);

	} while (++argv, --argc >= 1);

	return (nerror != 0);
}

/* Function: Print revisions chosen, which are in trunk
 */
void putrunk(void)
{
	struct hshentry *ptr, *pre;

	if (Head == nil)
		return;		/* empty tree  */

	pre = Head;
	ptr = Head->next;
	while (ptr)
	{
		putadelta(pre, ptr, true);
		pre = ptr;
		ptr = ptr->next;
	}
	putadelta(pre, ptr, true);
}


/* Function: Print delta tree (not including trunk) in reversed
 * calendar order on each branch
 */
void putree(struct hshentry * root)
{
	if (root == nil)
		return;

	putree(root->next);

	putforest(root->branches);
}

/* Function: Print branches that have the same direct ancestor
 */
void putforest(struct branchhead * branchroot)
{

	if (branchroot == nil)
		return;

	putforest(branchroot->nextbranch);

	putabranch(branchroot->hsh);
	putree(branchroot->hsh);
}

/* Function: Print one branch
 */
void putabranch(struct hshentry * root)
{

	if (root == nil)
		return;

	putabranch(root->next);

	putadelta(root, root, false);
}

/* Function: Print delta node if node->selector is 's'
 * editscript indicates where the editscript is stored
 * trunk indicates whether this node is in trunk
 */
void putadelta(struct hshentry * node, struct hshentry * editscript, int trunk)
{
	struct branchhead *newbranch;
	char *branchnum, branch[40];

	if ((node == nil) || (node->selector == 'u'))
		return;

	VOID fprintf(stdout, "----------------------------\n");
	VOID fprintf(stdout, "revision %s        ", node->num);
	if (node->lockedby)
		VOID fprintf(stdout, "locked by: %s;       ", node->lockedby);
	VOID putc('\n', stdout);

	VOID fputs("date: ", stdout);
	VOID PRINTDATE(stdout, node->date);
	VOID putc(' ', stdout);
	VOID PRINTTIME(stdout, node->date);
	VOID fprintf(stdout, ";  author: %s;  ", node->author);
	VOID fprintf(stdout, "state: %s;  ", node->state);

	if (editscript)
	{
		if (trunk)
			VOID fprintf(stdout, "lines added/del: %d/%d",
			      editscript->deletelns, editscript->insertlns);
		else
			VOID fprintf(stdout, "lines added/del: %d/%d",
			      editscript->insertlns, editscript->deletelns);
	}

	VOID putc('\n', stdout);

	branchnum = &(branch[0]);
	newbranch = node->branches;
	if (newbranch)
	{
		VOID fputs("branches:  ", stdout);
		while (newbranch)
		{
			getbranchno(newbranch->hsh->num, branchnum);
			VOID fprintf(stdout, "%s;  ", branchnum);
			newbranch = newbranch->nextbranch;
		}
		VOID putc('\n', stdout);
	}

	VOID fputs(node->log, stdout);
}

/* Function: get the log message and skip the text of a deltatext node.
 * Return false if current block does not start with a number.
 * Assumes the current lexeme is not yet in nexttok; does not advance nexttok.
 */
int readdeltalog(void)
{
	register struct hshentry *Delta;

	nextlex();
	if ((Delta = getnum()) == nil)
		return (false);
	if (!getkey(Klog) || (nexttok != STRING))
		fatserror("Missing log entry");
	Delta->log = malloc(logsize);
	VOID savestring(Delta->log, logsize);
	nextlex();
	if (!getkey(Ktext) || (nexttok != STRING))
		fatserror("Missing delta text");
	Delta->insertlns = Delta->deletelns = 0;
	if (Delta != Head)
		getscript(Delta);
	else
		readstring();
	return true;
}

/* Function: read edit script of Delta and count how many lines added
 * and deleted in the script
 */
void getscript(struct hshentry * Delta)
{
	int ed;			/* editor command  */
	register int c;
	register int i;
	int length;

	while ((ed = getc(finptr)) != EOF)
	{
		/* assume first none white character is command name  */
		while (ed == '\n' || ed == ' ' || ed == '\t')
			ed = getc(finptr);
		if (ed == SDELIM)
			break;	/* script text is ended   */
		while ((c = getc(finptr)) == ' ');	/* skip blank  */
		if (!('0' <= c && c <= '9'))
		{
			faterror("Missing line number in edit script");
			break;
		}
		while ('0' <= (c = getc(finptr)) && c <= '9');

		while (c == ' ')
			c = getc(finptr);	/* skip blanks  */
		if (!('0' <= c && c <= '9'))
		{
			faterror("Incorrect range in edit script");
			break;
		}
		length = c - '0';
		while ('0' <= (c = getc(finptr)) && c <= '9')
			length = length * 10 + c - '0';
		while (c != '\n' && c != EOF)
			c = getc(finptr);
		switch (ed)
		{
		case 'd':
			Delta->deletelns += length;
			break;

		case 'a':
			/* skip scripted lines  */
			for (i = length; i > 0 && c != EOF; i--)
			{
				while ((c = getc(finptr)) != '\n' && c != EOF);
				Delta->insertlns++;
			}
			break;

		default:
			faterror("Unknown command in edit script: %c", ed);
			break;
		}
	}
	nextc = getc(finptr);
}

/* Function: Select revisions, starting with root
 */
void exttree(struct hshentry * root)
{
	struct branchhead *newbranch;

	if (root == nil)
		return;

	extractdelta(root);
	exttree(root->next);

	newbranch = root->branches;
	while (newbranch)
	{
		exttree(newbranch->hsh);
		newbranch = newbranch->nextbranch;
	}
}

/* Function: get the login names of lockers from command line
 * and store in lockerlist.
 */
void getlocker(char *argv)
{
	register char c;
	struct lockers *newlocker;
	argv--;
	while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
	       c == '\n' || c == ';');
	if (c == '\0')
	{
		lockerlist = nil;
		return;
	}

	while (c != '\0')
	{
		newlocker = (struct lockers *) malloc(sizeof(struct lockers));
		newlocker->lockerlink = lockerlist;
		newlocker->login = argv;
		lockerlist = newlocker;
		while ((c = (*++argv)) != ',' && c != '\0' && c != ' '
		       && c != '\t' && c != '\n' && c != ';');
		*argv = '\0';
		if (c == '\0')
			return;
		while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
		       c == '\n' || c == ';');
	}
}

/* Function: Get the author's name form command line and store in authorlist
 */
void getauthor(char *argv)
{
	register c;
	struct authors *newauthor;

	argv--;
	while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
	       c == '\n' || c == ';');
	if (c == '\0')
	{
		authorlist = (struct authors *) malloc(sizeof(struct authors));
		authorlist->login = login_name;
		authorlist->nextauthor = nil;
		return;
	}

	while (c != '\0')
	{
		newauthor = (struct authors *) malloc(sizeof(struct authors));
		newauthor->nextauthor = authorlist;
		newauthor->login = argv;
		authorlist = newauthor;
		while ((c = *++argv) != ',' && c != '\0' && c != ' '
		       && c != '\t' && c != '\n' && c != ';');
		*argv = '\0';
		if (c == '\0')
			return;
		while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
		       c == '\n' || c == ';');
	}
}

/* Function: get the states of revisions from command line
 * and store in statelist
 */
void getstate(char *argv)
{
	register char c;
	struct stateattri *newstate;

	argv--;
	while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
	       c == '\n' || c == ';');
	if (c == '\0')
	{
		warn(" Missing state attributes after -s options");
		return;
	}

	while (c != '\0')
	{
		newstate = (struct stateattri *) malloc(sizeof(struct stateattri));
		newstate->nextstate = statelist;
		newstate->status = argv;
		statelist = newstate;
		while ((c = (*++argv)) != ',' && c != '\0' && c != ' '
		       && c != '\t' && c != '\n' && c != ';');
		*argv = '\0';
		if (c == '\0')
			return;
		while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
		       c == '\n' || c == ';');
	}
}

/* Function: Truncate the list of locks to those that are held by the
 * id's on lockerlist. Do not truncate if lockerlist empty.
 */
void trunclocks(void)
{
	struct lockers *plocker;
	struct lock *plocked, *nextlocked;

	if ((lockerlist == nil) || (Locks == nil))
		return;

	/* shorten Locks to those contained in lockerlist */
	plocked = Locks;
	Locks = nil;
	while (plocked != nil)
	{
		plocker = lockerlist;
		while ((plocker != nil) && (strcmp(plocker->login, plocked->login) != 0))
			plocker = plocker->lockerlink;
		nextlocked = plocked->nextlock;
		if (plocker != nil)
		{
			plocked->nextlock = Locks;
			Locks = plocked;
		}
		plocked = nextlocked;
	}
}

/* Function: Finds the delta that is closest to the cutoff date given by
 * pd among the revisions selected by exttree. Successively narrows down
 * the interfal given by pd, and sets the strtdate of pd to the date of
 * the selected delta.
 */
void recentdate(struct hshentry * root, struct Datepairs * pd)
{
	struct branchhead *newbranch;

	if (root == nil)
		return;
	if (root->selector == 's')
	{
		if (cmpnum(root->date, pd->strtdate) >= 0 &&
		    cmpnum(root->date, pd->enddate) <= 0)
			VOID strcpy(pd->strtdate, root->date);
	}

	recentdate(root->next, pd);
	newbranch = root->branches;
	while (newbranch)
	{
		recentdate(newbranch->hsh, pd);
		newbranch = newbranch->nextbranch;
	}
}

/* Function: select revisions which are in the date range specified
 * in duelst and datelist, start at root
 */
void extdate(struct hshentry * root)
{
	struct branchhead *newbranch;
	struct Datepairs *pdate;

	if (root == nil)
		return;

	if (datelist || duelst)
	{
		pdate = datelist;
		while (pdate)
		{
			if ((pdate->strtdate)[0] == '\0' || cmpnum(root->date, pdate->strtdate) >= 0)
			{
				if ((pdate->enddate)[0] == '\0' || cmpnum(pdate->enddate, root->date) >= 0)
					break;
			}
			pdate = pdate->dnext;
		}
		if (pdate == nil)
		{
			pdate = duelst;
			while (pdate)
			{
				if (cmpnum(root->date, pdate->strtdate) == 0)
					break;
				pdate = pdate->dnext;
			}
		}
		if (pdate == nil)
			root->selector = 'u';
	}
	if (root->selector == 's')
		revno++;

	extdate(root->next);

	newbranch = root->branches;
	while (newbranch)
	{
		extdate(newbranch->hsh);
		newbranch = newbranch->nextbranch;
	}
}

/* Function: compare information of pdelta to the authorlst, lockerlist,
 * statelist, revlist and mark 's' on selector if pdelta is
 * selected; otherwise, mark 'u'
 */
void extractdelta(struct hshentry * pdelta)
{
	struct lock *plock;
	struct stateattri *pstate;
	struct authors *pauthor;
	struct Revpairs *prevision;
	int length;

	pdelta->selector = 's';
	if (authorlist)
	{			/* certain author's revisions wanted only  */
		pauthor = authorlist;
		while ((pauthor != nil) && (strcmp(pauthor->login, pdelta->author) != 0))
			pauthor = pauthor->nextauthor;
		if (pauthor == nil)
		{
			pdelta->selector = 'u';
			return;
		}
	}
	if (statelist)
	{			/* revisions with certain state wanted  */
		pstate = statelist;
		while ((pstate != nil) && (strcmp(pstate->status, pdelta->state) != 0))
			pstate = pstate->nextstate;
		if (pstate == nil)
		{
			pdelta->selector = 'u';
			return;
		}
	}
	if (lockflag)
	{			/* locked revisions   */
		plock = Locks;
		while (plock && (plock->delta != pdelta))
			plock = plock->nextlock;
		if (plock == nil)
		{
			pdelta->selector = 'u';
			return;
		}
	}
	if (Revlst)
	{			/* revisions or branches selected  */

		prevision = Revlst;
		while (prevision != nil)
		{
			length = prevision->numfld;
			if (length % 2 == 1)
			{	/* a branch number  */
				if (countnumflds(pdelta->num) == (length + 1))
					if ((compartial(pdelta->num, prevision->strtrev, length) >= 0) &&
					    (compartial(prevision->endrev, pdelta->num, length) >= 0))
						break;
			}
			else if (countnumflds(pdelta->num) == length)	/* a revision */
				if ((compartial(pdelta->num, prevision->strtrev, length) >= 0) &&
				    (compartial(prevision->endrev, pdelta->num, length) >= 0))
					break;
			prevision = prevision->rnext;
		}
		if (prevision == nil)
		{
			pdelta->selector = 'u';
			return;
		}
	}
}

/* Function: Parses a free-format date in target, converts it
 * into RCS internal format, and stores the result into source.
 * Returns target on success, nil otherwise.
 */
char *procdate(char *target, char *source)
{
	time_t unixtime;
	struct rcs_tm parseddate;
	struct tm *ftm;

	if (partime(source, &parseddate) == 0)
	{
		error("Can't parse date/time: %s", source);
		*target = '\0';
		return nil;
	}
	if ((unixtime = maketime(&parseddate)) == 0L)
	{
		error("Inconsistent date/time: %s", source);
		*target = '\0';
		return nil;
	}
	ftm = localtime(&unixtime);
	VOID sprintf(target, DATEFORM,
		         ftm->tm_year, ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, ftm->tm_min, ftm->tm_sec);
	return target;
}

/* Function: Get time range from command line and store in datelist if
 * a time range specified or in duelst if a time spot specified
 */
void getdatepair(char *argv)
{
	register char c;
	struct Datepairs *nextdate;
	char *rawdate;
	int switchflag;

	argv--;
	while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
	       c == '\n' || c == ';');
	if (c == '\0')
	{
		warn("Missing date/time after -d");
		return;
	}

	while (c != '\0')
	{
		switchflag = false;
		nextdate = (struct Datepairs *) malloc(sizeof(struct Datepairs));
		if (c == '<')
		{		/* case: -d <date   */
			c = *++argv;
			(nextdate->strtdate)[0] = '\0';
		}
		else if (c == '>')
		{		/* case:  -d >date     */
			c = *++argv;
			(nextdate->enddate)[0] = '\0';
			switchflag = true;
		}
		else
		{
			rawdate = argv;
			while (c != '<' && c != '>' && c != ';' && c != '\0')
				c = *++argv;
			*argv = '\0';
			if (c == '>')
				switchflag = true;
			if (procdate(switchflag ? nextdate->enddate : nextdate->strtdate,
				     rawdate) == nil)
				continue;
			if (c == ';' || c == '\0')
			{	/* case: -d date  */
				VOID strcpy(nextdate->enddate, nextdate->strtdate);
				VOID sprintf(nextdate->strtdate, DATEFORM, 0, 0, 0, 0, 0, 0);
				nextdate->dnext = duelst;
				duelst = nextdate;
				goto end;
			}
			else
			{
				/*
				 * case:   -d date<  or -d  date>; see
				 * switchflag
				 */
				while ((c = *++argv) == ' ' || c == '\t' || c == '\n');
				if (c == ';' || c == '\0')
				{
					/* second date missing */
					if (switchflag)
						*nextdate->strtdate = '\0';
					else
						*nextdate->enddate = '\0';
					nextdate->dnext = datelist;
					datelist = nextdate;
					goto end;
				}
			}
		}
		rawdate = argv;
		while (c != '>' && c != '<' && c != ';' && c != '\0')
			c = *++argv;
		*argv = '\0';
		if (procdate(switchflag ? nextdate->strtdate : nextdate->enddate,
			     rawdate) == nil)
			continue;
		nextdate->dnext = datelist;
		datelist = nextdate;
end:
#if 0
	    VOID printf("startdate: %s; enddate: %s;\n", nextdate->strtdate,nextdate->enddate);
#endif
		if (c == '\0')
			return;
		while ((c = *++argv) == ';' || c == ' ' || c == '\t' || c == '\n');
	}
}

/* Function: Get the numeric name of revisions which stored in revlist
 * and then stored the numeric names in Revlst if branchflag, also add
 * default branch.
 */
void getnumericrev(void)
{
	struct Revpairs *ptr, *pt;
	int flag;
	char *temprev;

	/* free the previous numeric revision list  */
	pt = Revlst;
	while (pt)
	{
		free((char *) pt);
		pt = pt->rnext;
	}
	Nextdotstring = &Dotstring[0];	/* reset buffer */


	Revlst = nil;
	ptr = revlist;
	while (ptr)
	{
		pt = (struct Revpairs *) malloc(sizeof(struct Revpairs));
		if (ptr->numfld == 1)
		{		/* case:  -r rev   */
			if ((flag = expandsym(ptr->strtrev, Nextdotstring)) == true)
			{
				pt->numfld = countnumflds(Nextdotstring);
				pt->strtrev = pt->endrev = Nextdotstring;
				while (*Nextdotstring++ != '\0');
			}
		}
		else if (ptr->numfld == 2)
		{		/* case: -r rev-   */
			if ((flag = expandsym(ptr->strtrev, Nextdotstring)) == true)
			{
				pt->numfld = countnumflds(Nextdotstring);
				pt->strtrev = Nextdotstring;
				while (*Nextdotstring++ != '\0');
				pt->endrev = Nextdotstring;
				if (pt->numfld > 2)
					choptail(pt->strtrev);
				*Nextdotstring++ = '\0';
			}
		}
		else if (ptr->numfld == 3)
		{		/* case: -r -rev   */
			if ((flag = expandsym(ptr->endrev, Nextdotstring)) == true)
			{
				pt->endrev = Nextdotstring;
				while (*Nextdotstring++ != '\0');
				pt->numfld = countnumflds(pt->endrev);
				pt->strtrev = Nextdotstring;
				if (pt->numfld == 2)
					*Nextdotstring++ = '1';
				else
					choptail(pt->endrev);
				*Nextdotstring++ = '.';
				*Nextdotstring++ = '1';
				*Nextdotstring++ = '\0';
			}
		}
		else
		{		/* case:  -r rev1-rev2   */
			if ((flag = expandsym(ptr->strtrev, Nextdotstring)) == true)
			{
				pt->strtrev = Nextdotstring;
				while (*Nextdotstring++ != '\0');
				if ((flag = expandsym(ptr->endrev, Nextdotstring)) == true)
				{
					pt->numfld = countnumflds(pt->strtrev);
					pt->endrev = Nextdotstring;
					while (*Nextdotstring++ != '\0');
					if ((flag = checkrevpair(pt->strtrev, pt->endrev)) == true)
						/*
						 * switch pt->strtrev with
						 * pt->endrev, if pt->strtrev
						 * > pt->endre
						 */
						if (compartial(pt->strtrev, pt->endrev, pt->numfld) > 0)
						{
							temprev = pt->strtrev;
							pt->strtrev = pt->endrev;
							pt->endrev = temprev;
						}
				}
			}
		}

		if (flag)
		{
			pt->rnext = Revlst;
			Revlst = pt;
		}
		else
			free((char *) pt);
		ptr = ptr->rnext;
	}
	/* Now take care of branchflag */
	if (branchflag)
	{
		flag = true;
		pt = (struct Revpairs *) malloc(sizeof(struct Revpairs));
		if (Dbranch)
		{
			pt->strtrev = pt->endrev = Dbranch->num;
		}
		else if (Head != nil)
		{
			pt->strtrev = pt->endrev =	/* branch number of head */
				partialno(Nextdotstring, Head->num, 1);
			while (*Nextdotstring++ != '\0');
		}
		else
			flag = false;
		if (flag)
		{		/* prepend new node */
			pt->rnext = Revlst;
			Revlst = pt;
			pt->numfld = countnumflds(pt->strtrev);
		}
	}

}

/* Function: Check whether num1, num2 are legal pair, ie. only the last
 * field are different and have same number of fields (if length <= 2,
 * may be different if first field)
 */
int checkrevpair(char *num1, char *num2)
{
	int length;

	if ((length = countnumflds(num1)) != countnumflds(num2))
	{
		error(" Invalid branch or revision pair %s : %s", num1, num2);
		return false;
	}
	if (length > 2)
		if (compartial(num1, num2, length - 1) != 0)
		{
			error("Invalid branch or revision pair %s : %s", num1, num2);
			return false;
		}

	return true;
}

/* Function: Get revision or branch range from command line,
 * and store in revlist
 */
void getrevpairs(char *argv)
{
	register char c;
	struct Revpairs *nextrevpair;
	int flag;

	argv--;
	while ((c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
	       c == '\n' || c == ';');
	if (c == '\0')
	{
		warn(" Missing revision or branch number after -r");
		return;
	}

	while (c != '\0')
	{
		while (c == ',' || c == ' ' || c == '\t' ||
		       c == '\n' || c == ';')
			c = *++argv;
		if (c == '\0')
			return;
		nextrevpair = (struct Revpairs *) malloc(sizeof(struct Revpairs));
		nextrevpair->rnext = revlist;
		revlist = nextrevpair;
		nextrevpair->numfld = nil;
		nextrevpair->strtrev = nil;
		nextrevpair->endrev = nil;
		flag = false;
		if (c == '<' || c == '-')
		{		/* case: -r -rev  or -r <rev  */
			flag = true;
			while ((c = (*++argv)) == ' ' || c == '\t' || c == '\n');
		}
		else
		{
			nextrevpair->strtrev = argv;
			/* get a revision or branch name  */
			while (c != ',' && c != ';' && c != ' ' && c != '\0' && c != '-'
			       && c != '\t' && c != '\n' && c != '<')
				c = *++argv;

			*argv = '\0';

			if (c != '<' && c != '-')
			{	/* case: rev  */
				nextrevpair->numfld = 1;
				continue;
			}

			if ((c = (*++argv)) == ',' || c == '\0' || c == ' '
			    || c == '\t' || c == '\n' || c == ';')
			{	/* case: rev_  */
				nextrevpair->numfld = 2;
				continue;
			}
		}
		nextrevpair->endrev = argv;
		while (c != ',' && c != ' ' && c != '\0' && c != '\t' && c != '<'
		       && c != '\n' && c != '-' && c != ';')
			c = *++argv;

		*argv = '\0';
		if (c == '<')
		{
			error("seperator expected near %s", nextrevpair->endrev);
			while ((c = *++argv) != ',' && c != ' ' && c != '\0' &&
			       c != '\t' && c != '\n' && c != ';');
			revlist = nextrevpair->rnext;
			continue;
		}
		else
		{
			if (flag)	/* case:  -rev   */
				nextrevpair->numfld = 3;

			else	/* rev1-rev2  appears  */
				nextrevpair->numfld = 4;
		}
	}
}

/* Function: Chop off the last field of a branch or a revision number
 */
void choptail(char *strhead)
{
	char *pt, *sp;

	for (pt = Nextdotstring - 1; pt != strhead && *pt != '.'; pt--);
	for (sp = strhead; sp < pt; sp++)
		*Nextdotstring++ = *sp;
}
