rlog.c revision 9
1/*
2 *                       RLOG    operation
3 */
4/*****************************************************************************
5 *                       print contents of RCS files
6 *****************************************************************************
7 */
8
9/* Copyright (C) 1982, 1988, 1989 Walter Tichy
10   Copyright 1990, 1991 by Paul Eggert
11   Distributed under license by the Free Software Foundation, Inc.
12
13This file is part of RCS.
14
15RCS is free software; you can redistribute it and/or modify
16it under the terms of the GNU General Public License as published by
17the Free Software Foundation; either version 2, or (at your option)
18any later version.
19
20RCS is distributed in the hope that it will be useful,
21but WITHOUT ANY WARRANTY; without even the implied warranty of
22MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23GNU General Public License for more details.
24
25You should have received a copy of the GNU General Public License
26along with RCS; see the file COPYING.  If not, write to
27the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
28
29Report problems and direct all questions to:
30
31    rcs-bugs@cs.purdue.edu
32
33*/
34
35
36
37
38/* $Log: rlog.c,v $
39 * Revision 5.9  1991/09/17  19:07:40  eggert
40 * Getscript() didn't uncache partial lines.
41 *
42 * Revision 5.8  1991/08/19  03:13:55  eggert
43 * Revision separator is `:', not `-'.
44 * Check for missing and duplicate logs.  Tune.
45 * Permit log messages that do not end in newline (including empty logs).
46 *
47 * Revision 5.7  1991/04/21  11:58:31  eggert
48 * Add -x, RCSINIT, MS-DOS support.
49 *
50 * Revision 5.6  1991/02/26  17:07:17  eggert
51 * Survive RCS files with missing logs.
52 * strsave -> str_save (DG/UX name clash)
53 *
54 * Revision 5.5  1990/11/01  05:03:55  eggert
55 * Permit arbitrary data in logs and comment leaders.
56 *
57 * Revision 5.4  1990/10/04  06:30:22  eggert
58 * Accumulate exit status across files.
59 *
60 * Revision 5.3  1990/09/11  02:41:16  eggert
61 * Plug memory leak.
62 *
63 * Revision 5.2  1990/09/04  08:02:33  eggert
64 * Count RCS lines better.
65 *
66 * Revision 5.0  1990/08/22  08:13:48  eggert
67 * Remove compile-time limits; use malloc instead.  Add setuid support.
68 * Switch to GMT.
69 * Report dates in long form, to warn about dates past 1999/12/31.
70 * Change "added/del" message to make room for the longer dates.
71 * Don't generate trailing white space.  Add -V.  Ansify and Posixate.
72 *
73 * Revision 4.7  89/05/01  15:13:48  narten
74 * changed copyright header to reflect current distribution rules
75 *
76 * Revision 4.6  88/08/09  19:13:28  eggert
77 * Check for memory exhaustion; don't access freed storage.
78 * Shrink stdio code size; remove lint.
79 *
80 * Revision 4.5  87/12/18  11:46:38  narten
81 * more lint cleanups (Guy Harris)
82 *
83 * Revision 4.4  87/10/18  10:41:12  narten
84 * Updating version numbers
85 * Changes relative to 1.1 actually relative to 4.2
86 *
87 * Revision 1.3  87/09/24  14:01:10  narten
88 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
89 * warnings)
90 *
91 * Revision 1.2  87/03/27  14:22:45  jenkins
92 * Port to suns
93 *
94 * Revision 4.2  83/12/05  09:18:09  wft
95 * changed rewriteflag to external.
96 *
97 * Revision 4.1  83/05/11  16:16:55  wft
98 * Added -b, updated getnumericrev() accordingly.
99 * Replaced getpwuid() with getcaller().
100 *
101 * Revision 3.7  83/05/11  14:24:13  wft
102 * Added options -L and -R;
103 * Fixed selection bug with -l on multiple files.
104 * Fixed error on dates of the form -d'>date' (rewrote getdatepair()).
105 *
106 * Revision 3.6  82/12/24  15:57:53  wft
107 * shortened output format.
108 *
109 * Revision 3.5  82/12/08  21:45:26  wft
110 * removed call to checkaccesslist(); used DATEFORM to format all dates;
111 * removed unused variables.
112 *
113 * Revision 3.4  82/12/04  13:26:25  wft
114 * Replaced getdelta() with gettree(); removed updating of field lockedby.
115 *
116 * Revision 3.3  82/12/03  14:08:20  wft
117 * Replaced getlogin with getpwuid(), %02d with %.2d, fancydate with PRINTDATE.
118 * Fixed printing of nil, removed printing of Suffix,
119 * added shortcut if no revisions are printed, disambiguated struct members.
120 *
121 * Revision 3.2  82/10/18  21:09:06  wft
122 * call to curdir replaced with getfullRCSname(),
123 * fixed call to getlogin(), cosmetic changes on output,
124 * changed conflicting long identifiers.
125 *
126 * Revision 3.1  82/10/13  16:07:56  wft
127 * fixed type of variables receiving from getc() (char -> int).
128 */
129
130
131
132#include "rcsbase.h"
133
134struct  lockers {                     /* lockers in locker option; stored   */
135     char const		* login;      /* lockerlist			    */
136     struct     lockers * lockerlink;
137     }  ;
138
139struct  stateattri {                  /* states in state option; stored in  */
140     char const		* status;     /* statelist			    */
141     struct  stateattri * nextstate;
142     }  ;
143
144struct  authors {                     /* login names in author option;      */
145     char const		* login;      /* stored in authorlist		    */
146     struct     authors * nextauthor;
147     }  ;
148
149struct Revpairs{                      /* revision or branch range in -r     */
150     unsigned		  numfld;     /* option; stored in revlist	    */
151     char const		* strtrev;
152     char const		* endrev;
153     struct  Revpairs   * rnext;
154     } ;
155
156struct Datepairs{                     /* date range in -d option; stored in */
157     char               strtdate[datesize];   /* duelst and datelist      */
158     char               enddate[datesize];
159     struct  Datepairs  * dnext;
160     };
161
162static char extractdelta P((struct hshentry const*));
163static int checkrevpair P((char const*,char const*));
164static struct hshentry const *readdeltalog P((void));
165static unsigned extdate P((struct hshentry*));
166static void cleanup P((void));
167static void exttree P((struct hshentry*));
168static void getauthor P((char*));
169static void getdatepair P((char*));
170static void getlocker P((char*));
171static void getnumericrev P((void));
172static void getrevpairs P((char*));
173static void getscript P((struct hshentry*));
174static void getstate P((char*));
175static void putabranch P((struct hshentry const*));
176static void putadelta P((struct hshentry const*,struct hshentry const*,int));
177static void putforest P((struct branchhead const*));
178static void putree P((struct hshentry const*));
179static void putrunk P((void));
180static void recentdate P((struct hshentry const*,struct Datepairs*));
181static void trunclocks P((void));
182
183static char const *insDelFormat;
184static int branchflag;	/*set on -b */
185static int exitstatus;
186static int lockflag;
187static struct Datepairs *datelist, *duelst;
188static struct Revpairs *revlist, *Revlst;
189static struct authors *authorlist;
190static struct lockers *lockerlist;
191static struct stateattri *statelist;
192
193
194mainProg(rlogId, "rlog", "$Id: rlog.c,v 5.9 1991/09/17 19:07:40 eggert Exp $")
195{
196	static char const cmdusage[] =
197		"\nrlog usage: rlog -{bhLRt} -ddates -l[lockers] -rrevs -sstates -w[logins] -Vn file ...";
198
199	register FILE *out;
200	char *a, **newargv;
201	struct Datepairs *currdate;
202	char const *accessListString, *accessFormat, *commentFormat;
203	char const *headFormat, *symbolFormat;
204	struct access const *curaccess;
205	struct assoc const *curassoc;
206	struct hshentry const *delta;
207	struct lock const *currlock;
208	int descflag, selectflag;
209	int onlylockflag;  /* print only files with locks */
210	int onlyRCSflag;  /* print only RCS file name */
211	unsigned revno;
212
213        descflag = selectflag = true;
214	onlylockflag = onlyRCSflag = false;
215	out = stdout;
216	suffixes = X_DEFAULT;
217
218	argc = getRCSINIT(argc, argv, &newargv);
219	argv = newargv;
220	while (a = *++argv,  0<--argc && *a++=='-') {
221		switch (*a++) {
222
223		case 'L':
224			onlylockflag = true;
225			break;
226
227		case 'R':
228			onlyRCSflag =true;
229			break;
230
231                case 'l':
232                        lockflag = true;
233			getlocker(a);
234                        break;
235
236                case 'b':
237                        branchflag = true;
238                        break;
239
240                case 'r':
241			getrevpairs(a);
242                        break;
243
244                case 'd':
245			getdatepair(a);
246                        break;
247
248                case 's':
249			getstate(a);
250                        break;
251
252                case 'w':
253			getauthor(a);
254                        break;
255
256                case 'h':
257			descflag = false;
258                        break;
259
260                case 't':
261                        selectflag = false;
262                        break;
263
264		case 'q':
265			/* This has no effect; it's here for consistency.  */
266			quietflag = true;
267			break;
268
269		case 'x':
270			suffixes = a;
271			break;
272
273		case 'V':
274			setRCSversion(*argv);
275			break;
276
277                default:
278			faterror("unknown option: %s%s", *argv, cmdusage);
279
280                };
281        } /* end of option processing */
282
283	if (argc<1) faterror("no input file%s", cmdusage);
284
285	if (! (descflag|selectflag)) {
286		warn("-t overrides -h.");
287		descflag = true;
288	}
289
290	if (RCSversion < VERSION(5)) {
291	    accessListString = "\naccess list:   ";
292	    accessFormat = "  %s";
293	    commentFormat = "\ncomment leader:  \"";
294	    headFormat = "\nRCS file:        %s;   Working file:    %s\nhead:           %s%s\nbranch:         %s%s\nlocks:         ";
295	    insDelFormat = "  lines added/del: %lu/%lu";
296	    symbolFormat = "  %s: %s;";
297	} else {
298	    accessListString = "\naccess list:";
299	    accessFormat = "\n\t%s";
300	    commentFormat = "\ncomment leader: \"";
301	    headFormat = "\nRCS file: %s\nWorking file: %s\nhead:%s%s\nbranch:%s%s\nlocks:%s";
302	    insDelFormat = "  lines: +%lu -%lu";
303	    symbolFormat = "\n\t%s: %s";
304	}
305
306        /* now handle all filenames */
307        do {
308	    ffree();
309
310	    if (pairfilenames(argc, argv, rcsreadopen, true, false)  <=  0)
311		continue;
312
313            /* now RCSfilename contains the name of the RCS file, and finptr
314             * the file descriptor. Workfilename contains the name of the
315             * working file.
316             */
317
318	    /* Keep only those locks given by -l.  */
319	    if (lockflag)
320		trunclocks();
321
322            /* do nothing if -L is given and there are no locks*/
323	    if (onlylockflag && !Locks)
324		continue;
325
326	    if ( onlyRCSflag ) {
327		aprintf(out, "%s\n", RCSfilename);
328		continue;
329	    }
330            /*   print RCS filename , working filename and optional
331                 administrative information                         */
332            /* could use getfullRCSname() here, but that is very slow */
333	    aprintf(out, headFormat, RCSfilename, workfilename,
334		    Head ? " " : "",  Head ? Head->num : "",
335		    Dbranch ? " " : "",  Dbranch ? Dbranch : "",
336		    StrictLocks ? " strict" : ""
337	    );
338            currlock = Locks;
339            while( currlock ) {
340		aprintf(out, symbolFormat, currlock->login,
341                                currlock->delta->num);
342                currlock = currlock->nextlock;
343            }
344            if (StrictLocks && RCSversion<VERSION(5))
345		aputs("  strict", out);
346
347	    aputs(accessListString, out);      /*  print access list  */
348            curaccess = AccessList;
349            while(curaccess) {
350		aprintf(out, accessFormat, curaccess->login);
351                curaccess = curaccess->nextaccess;
352            }
353
354	    aputs("\nsymbolic names:", out);   /*  print symbolic names   */
355	    for (curassoc=Symbols; curassoc; curassoc=curassoc->nextassoc)
356		aprintf(out, symbolFormat, curassoc->symbol, curassoc->num);
357	    aputs(commentFormat, out);
358	    awrite(Comment.string, Comment.size, out);
359	    aputs("\"\n", out);
360	    if (VERSION(5)<=RCSversion  ||  Expand != KEYVAL_EXPAND)
361		aprintf(out, "keyword substitution: %s\n",
362			expand_names[Expand]
363		);
364
365            gettree();
366
367	    aprintf(out, "total revisions: %u", TotalDeltas);
368
369	    revno = 0;
370
371	    if (Head  &&  selectflag & descflag) {
372
373		getnumericrev();    /* get numeric revision or branch names */
374
375		exttree(Head);
376
377		/*  get most recently date of the dates pointed by duelst  */
378		currdate = duelst;
379		while( currdate) {
380		    VOID sprintf(currdate->strtdate,DATEFORM,0,0,0,0,0,0);
381		    recentdate(Head, currdate);
382		    currdate = currdate->dnext;
383		}
384
385		revno = extdate(Head);
386
387		aprintf(out, ";\tselected revisions: %u", revno);
388	    }
389
390	    afputc('\n',out);
391	    if (descflag) {
392		aputs("description:\n", out);
393		getdesc(true);
394	    }
395	    if (revno) {
396		while (! (delta = readdeltalog())->selector  ||  --revno)
397		    ;
398		if (delta->next && countnumflds(delta->num)==2)
399		    /* Read through delta->next to get its insertlns.  */
400		    while (readdeltalog() != delta->next)
401			;
402		putrunk();
403		putree(Head);
404	    }
405	    aputs("=============================================================================\n",out);
406	} while (cleanup(),
407		 ++argv, --argc >= 1);
408	Ofclose(out);
409	exitmain(exitstatus);
410}
411
412	static void
413cleanup()
414{
415	if (nerror) exitstatus = EXIT_FAILURE;
416	Izclose(&finptr);
417}
418
419#if lint
420#	define exiterr rlogExit
421#endif
422	exiting void
423exiterr()
424{
425	_exit(EXIT_FAILURE);
426}
427
428
429
430	static void
431putrunk()
432/*  function:  print revisions chosen, which are in trunk      */
433
434{
435	register struct hshentry const *ptr;
436
437	for (ptr = Head;  ptr;  ptr = ptr->next)
438		putadelta(ptr, ptr->next, true);
439}
440
441
442
443	static void
444putree(root)
445	struct hshentry const *root;
446/*   function: print delta tree (not including trunk) in reverse
447               order on each branch                                        */
448
449{
450        if ( root == nil ) return;
451
452        putree(root->next);
453
454        putforest(root->branches);
455}
456
457
458
459
460	static void
461putforest(branchroot)
462	struct branchhead const *branchroot;
463/*   function:  print branches that has the same direct ancestor    */
464{
465
466        if ( branchroot == nil ) return;
467
468        putforest(branchroot->nextbranch);
469
470        putabranch(branchroot->hsh);
471        putree(branchroot->hsh);
472}
473
474
475
476
477	static void
478putabranch(root)
479	struct hshentry const *root;
480/*   function  :  print one branch     */
481
482{
483
484        if ( root == nil) return;
485
486        putabranch(root->next);
487
488        putadelta(root, root, false);
489}
490
491
492
493
494
495	static void
496putadelta(node,editscript,trunk)
497	register struct hshentry const *node, *editscript;
498	int trunk;
499/*  function: Print delta node if node->selector is set.        */
500/*      editscript indicates where the editscript is stored     */
501/*      trunk indicated whether this node is in trunk           */
502{
503	static char emptych[] = EMPTYLOG;
504
505	register FILE *out;
506	char const *s;
507	size_t n;
508	struct branchhead const *newbranch;
509	struct buf branchnum;
510	char datebuf[datesize];
511
512	if (!node->selector)
513            return;
514
515	out = stdout;
516	aprintf(out,
517		"----------------------------\nrevision %s", node->num
518	);
519        if ( node->lockedby )
520	   aprintf(out, "\tlocked by: %s;", node->lockedby);
521
522	aprintf(out, "\ndate: %s;  author: %s;  state: %s;",
523		date2str(node->date, datebuf),
524		node->author, node->state
525	);
526
527        if ( editscript )
528           if(trunk)
529	      aprintf(out, insDelFormat,
530                             editscript->deletelns, editscript->insertlns);
531           else
532	      aprintf(out, insDelFormat,
533                             editscript->insertlns, editscript->deletelns);
534
535        newbranch = node->branches;
536        if ( newbranch ) {
537	   bufautobegin(&branchnum);
538	   aputs("\nbranches:", out);
539           while( newbranch ) {
540		getbranchno(newbranch->hsh->num, &branchnum);
541		aprintf(out, "  %s;", branchnum.string);
542                newbranch = newbranch->nextbranch;
543           }
544	   bufautoend(&branchnum);
545        }
546
547	afputc('\n', out);
548	s = node->log.string;
549	if (!(n = node->log.size)) {
550		s = emptych;
551		n = sizeof(emptych)-1;
552	}
553	awrite(s, n, out);
554	if (s[n-1] != '\n')
555		afputc('\n', out);
556}
557
558
559
560
561
562	static struct hshentry const *
563readdeltalog()
564/*  Function : get the log message and skip the text of a deltatext node.
565 *	       Return the delta found.
566 *             Assumes the current lexeme is not yet in nexttok; does not
567 *             advance nexttok.
568 */
569{
570        register struct  hshentry  * Delta;
571	struct buf logbuf;
572	struct cbuf cb;
573
574	if (eoflex())
575		fatserror("missing delta log");
576        nextlex();
577	if (!(Delta = getnum()))
578		fatserror("delta number corrupted");
579	getkeystring(Klog);
580	if (Delta->log.string)
581		fatserror("duplicate delta log");
582	bufautobegin(&logbuf);
583	cb = savestring(&logbuf);
584	Delta->log = bufremember(&logbuf, cb.size);
585
586        nextlex();
587	while (nexttok==ID && strcmp(NextString,Ktext)!=0)
588		ignorephrase();
589	getkeystring(Ktext);
590        Delta->insertlns = Delta->deletelns = 0;
591        if ( Delta != Head)
592                getscript(Delta);
593        else
594                readstring();
595	return Delta;
596}
597
598
599	static void
600getscript(Delta)
601struct    hshentry   * Delta;
602/*   function:  read edit script of Delta and count how many lines added  */
603/*              and deleted in the script                                 */
604
605{
606        int ed;   /*  editor command  */
607	declarecache;
608	register RILE *fin;
609        register  int   c;
610	register unsigned long i;
611	struct diffcmd dc;
612
613	fin = finptr;
614	setupcache(fin);
615	initdiffcmd(&dc);
616	while (0  <=  (ed = getdiffcmd(fin,true,(FILE *)0,&dc)))
617	    if (!ed)
618                 Delta->deletelns += dc.nlines;
619	    else {
620                 /*  skip scripted lines  */
621		 i = dc.nlines;
622		 Delta->insertlns += i;
623		 cache(fin);
624		 do {
625		     for (;;) {
626			cacheget(c);
627			switch (c) {
628			    default:
629				continue;
630			    case SDELIM:
631				cacheget(c);
632				if (c == SDELIM)
633				    continue;
634				if (--i)
635				    fatserror("unexpected end to edit script");
636				nextc = c;
637				uncache(fin);
638				return;
639			    case '\n':
640				break;
641			}
642			break;
643		     }
644		     ++rcsline;
645		 } while (--i);
646		 uncache(fin);
647            }
648}
649
650
651
652
653
654
655
656	static void
657exttree(root)
658struct hshentry  *root;
659/*  function: select revisions , starting with root             */
660
661{
662	struct branchhead const *newbranch;
663
664        if (root == nil) return;
665
666	root->selector = extractdelta(root);
667	root->log.string = nil;
668        exttree(root->next);
669
670        newbranch = root->branches;
671        while( newbranch ) {
672            exttree(newbranch->hsh);
673            newbranch = newbranch->nextbranch;
674        }
675}
676
677
678
679
680	static void
681getlocker(argv)
682char    * argv;
683/*   function : get the login names of lockers from command line   */
684/*              and store in lockerlist.                           */
685
686{
687        register char c;
688        struct   lockers   * newlocker;
689        argv--;
690        while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
691                 c == '\n' || c == ';')  ;
692        if (  c == '\0') {
693            lockerlist=nil;
694            return;
695        }
696
697        while( c != '\0' ) {
698	    newlocker = talloc(struct lockers);
699            newlocker->lockerlink = lockerlist;
700            newlocker->login = argv;
701            lockerlist = newlocker;
702            while ( ( c = (*++argv)) != ',' && c != '\0' && c != ' '
703                       && c != '\t' && c != '\n' && c != ';') ;
704            *argv = '\0';
705            if ( c == '\0' ) return;
706            while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
707                     c == '\n' || c == ';')  ;
708        }
709}
710
711
712
713	static void
714getauthor(argv)
715char   *argv;
716/*   function:  get the author's name from command line   */
717/*              and store in authorlist                   */
718
719{
720        register    c;
721        struct     authors  * newauthor;
722
723        argv--;
724        while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
725                 c == '\n' || c == ';')  ;
726        if ( c == '\0' ) {
727	    authorlist = talloc(struct authors);
728	    authorlist->login = getusername(false);
729            authorlist->nextauthor  = nil;
730            return;
731        }
732
733        while( c != '\0' ) {
734	    newauthor = talloc(struct authors);
735            newauthor->nextauthor = authorlist;
736            newauthor->login = argv;
737            authorlist = newauthor;
738            while( ( c = *++argv) != ',' && c != '\0' && c != ' '
739                     && c != '\t' && c != '\n' && c != ';') ;
740            * argv = '\0';
741            if ( c == '\0') return;
742            while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
743                     c == '\n' || c == ';')  ;
744        }
745}
746
747
748
749
750	static void
751getstate(argv)
752char   * argv;
753/*   function :  get the states of revisions from command line  */
754/*               and store in statelist                         */
755
756{
757        register  char  c;
758        struct    stateattri    *newstate;
759
760        argv--;
761        while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
762                 c == '\n' || c == ';')  ;
763        if ( c == '\0'){
764	    warn("missing state attributes after -s options");
765            return;
766        }
767
768        while( c != '\0' ) {
769	    newstate = talloc(struct stateattri);
770            newstate->nextstate = statelist;
771            newstate->status = argv;
772            statelist = newstate;
773            while( (c = (*++argv)) != ',' && c != '\0' && c != ' '
774                    && c != '\t' && c != '\n' && c != ';')  ;
775            *argv = '\0';
776            if ( c == '\0' ) return;
777            while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
778                     c == '\n' || c == ';')  ;
779        }
780}
781
782
783
784	static void
785trunclocks()
786/*  Function:  Truncate the list of locks to those that are held by the  */
787/*             id's on lockerlist. Do not truncate if lockerlist empty.  */
788
789{
790	struct lockers const *plocker;
791        struct lock     * plocked,  * nextlocked;
792
793        if ( (lockerlist == nil) || (Locks == nil)) return;
794
795        /* shorten Locks to those contained in lockerlist */
796        plocked = Locks;
797        Locks = nil;
798        while( plocked != nil) {
799            plocker = lockerlist;
800            while((plocker != nil) && ( strcmp(plocker->login, plocked->login)!=0))
801                plocker = plocker->lockerlink;
802            nextlocked = plocked->nextlock;
803            if ( plocker != nil) {
804                plocked->nextlock = Locks;
805                Locks = plocked;
806            }
807            plocked = nextlocked;
808        }
809}
810
811
812
813	static void
814recentdate(root, pd)
815	struct hshentry const *root;
816	struct Datepairs *pd;
817/*  function:  Finds the delta that is closest to the cutoff date given by   */
818/*             pd among the revisions selected by exttree.                   */
819/*             Successively narrows down the interval given by pd,           */
820/*             and sets the strtdate of pd to the date of the selected delta */
821{
822	struct branchhead const *newbranch;
823
824	if ( root == nil) return;
825	if (root->selector) {
826             if ( cmpnum(root->date, pd->strtdate) >= 0 &&
827                  cmpnum(root->date, pd->enddate) <= 0)
828		VOID strcpy(pd->strtdate, root->date);
829        }
830
831        recentdate(root->next, pd);
832        newbranch = root->branches;
833        while( newbranch) {
834           recentdate(newbranch->hsh, pd);
835           newbranch = newbranch->nextbranch;
836	}
837}
838
839
840
841
842
843
844	static unsigned
845extdate(root)
846struct  hshentry        * root;
847/*  function:  select revisions which are in the date range specified     */
848/*             in duelst  and datelist, start at root                     */
849/* Yield number of revisions selected, including those already selected.  */
850{
851	struct branchhead const *newbranch;
852	struct Datepairs const *pdate;
853	unsigned revno;
854
855	if (!root)
856	    return 0;
857
858        if ( datelist || duelst) {
859            pdate = datelist;
860            while( pdate ) {
861                if ( (pdate->strtdate)[0] == '\0' || cmpnum(root->date,pdate->strtdate) >= 0){
862                   if ((pdate->enddate)[0] == '\0' || cmpnum(pdate->enddate,root->date) >= 0)
863                        break;
864                }
865                pdate = pdate->dnext;
866            }
867            if ( pdate == nil) {
868                pdate = duelst;
869		for (;;) {
870		   if (!pdate) {
871			root->selector = false;
872			break;
873		   }
874                   if ( cmpnum(root->date, pdate->strtdate) == 0)
875                      break;
876                   pdate = pdate->dnext;
877                }
878            }
879        }
880	revno = root->selector + extdate(root->next);
881
882        newbranch = root->branches;
883        while( newbranch ) {
884	   revno += extdate(newbranch->hsh);
885           newbranch = newbranch->nextbranch;
886        }
887	return revno;
888}
889
890
891
892	static char
893extractdelta(pdelta)
894	struct hshentry const *pdelta;
895/*  function:  compare information of pdelta to the authorlist, lockerlist,*/
896/*             statelist, revlist and yield true if pdelta is selected.    */
897
898{
899	struct lock const *plock;
900	struct stateattri const *pstate;
901	struct authors const *pauthor;
902	struct Revpairs const *prevision;
903	unsigned length;
904
905	if ((pauthor = authorlist)) /* only certain authors wanted */
906	    while (strcmp(pauthor->login, pdelta->author) != 0)
907		if (!(pauthor = pauthor->nextauthor))
908		    return false;
909	if ((pstate = statelist)) /* only certain states wanted */
910	    while (strcmp(pstate->status, pdelta->state) != 0)
911		if (!(pstate = pstate->nextstate))
912		    return false;
913	if (lockflag) /* only locked revisions wanted */
914	    for (plock = Locks;  ;  plock = plock->nextlock)
915		if (!plock)
916		    return false;
917		else if (plock->delta == pdelta)
918		    break;
919	if ((prevision = Revlst)) /* only certain revs or branches wanted */
920	    for (;;) {
921                length = prevision->numfld;
922		if (
923		    countnumflds(pdelta->num) == length+(length&1) &&
924		    0 <= compartial(pdelta->num, prevision->strtrev, length) &&
925		    0 <= compartial(prevision->endrev, pdelta->num, length)
926		)
927		     break;
928		if (!(prevision = prevision->rnext))
929		    return false;
930            }
931	return true;
932}
933
934
935
936	static void
937getdatepair(argv)
938   char   * argv;
939/*  function:  get time range from command line and store in datelist if    */
940/*             a time range specified or in duelst if a time spot specified */
941
942{
943        register   char         c;
944        struct     Datepairs    * nextdate;
945	char const		* rawdate;
946	int                     switchflag;
947
948        argv--;
949        while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
950                 c == '\n' || c == ';')  ;
951        if ( c == '\0' ) {
952	    warn("missing date/time after -d");
953            return;
954        }
955
956        while( c != '\0' )  {
957	    switchflag = false;
958	    nextdate = talloc(struct Datepairs);
959            if ( c == '<' ) {   /*   case: -d <date   */
960                c = *++argv;
961                (nextdate->strtdate)[0] = '\0';
962	    } else if (c == '>') { /* case: -d'>date' */
963		c = *++argv;
964		(nextdate->enddate)[0] = '\0';
965		switchflag = true;
966	    } else {
967                rawdate = argv;
968		while( c != '<' && c != '>' && c != ';' && c != '\0')
969		     c = *++argv;
970                *argv = '\0';
971		if ( c == '>' ) switchflag=true;
972		str2date(rawdate,
973			 switchflag ? nextdate->enddate : nextdate->strtdate);
974		if ( c == ';' || c == '\0') {  /*  case: -d date  */
975		    VOID strcpy(nextdate->enddate,nextdate->strtdate);
976                    nextdate->dnext = duelst;
977                    duelst = nextdate;
978		    goto end;
979		} else {
980		    /*   case:   -d date<  or -d  date>; see switchflag */
981		    while ( (c= *++argv) == ' ' || c=='\t' || c=='\n');
982		    if ( c == ';' || c == '\0') {
983			/* second date missing */
984			if (switchflag)
985			    *nextdate->strtdate= '\0';
986			else
987			    *nextdate->enddate= '\0';
988			nextdate->dnext = datelist;
989			datelist = nextdate;
990			goto end;
991		    }
992                }
993            }
994            rawdate = argv;
995	    while( c != '>' && c != '<' && c != ';' && c != '\0')
996 		c = *++argv;
997            *argv = '\0';
998	    str2date(rawdate,
999		     switchflag ? nextdate->strtdate : nextdate->enddate);
1000            nextdate->dnext = datelist;
1001	    datelist = nextdate;
1002     end:
1003	    if ( c == '\0')  return;
1004            while( (c = *++argv) == ';' || c == ' ' || c == '\t' || c =='\n');
1005        }
1006}
1007
1008
1009
1010	static void
1011getnumericrev()
1012/*  function:  get the numeric name of revisions which stored in revlist  */
1013/*             and then stored the numeric names in Revlst                */
1014/*             if branchflag, also add default branch                     */
1015
1016{
1017        struct  Revpairs        * ptr, *pt;
1018	unsigned n;
1019	struct buf s, e;
1020	char const *lrev;
1021	struct buf const *rstart, *rend;
1022
1023        Revlst = nil;
1024        ptr = revlist;
1025	bufautobegin(&s);
1026	bufautobegin(&e);
1027        while( ptr ) {
1028	    n = 0;
1029	    rstart = &s;
1030	    rend = &e;
1031
1032	    switch (ptr->numfld) {
1033
1034	      case 1: /* -r rev */
1035		if (expandsym(ptr->strtrev, &s)) {
1036		    rend = &s;
1037		    n = countnumflds(s.string);
1038		    if (!n  &&  (lrev = tiprev())) {
1039			bufscpy(&s, lrev);
1040			n = countnumflds(lrev);
1041		    }
1042                }
1043		break;
1044
1045	      case 2: /* -r rev- */
1046		if (expandsym(ptr->strtrev, &s)) {
1047		    bufscpy(&e, s.string);
1048		    n = countnumflds(s.string);
1049		    (n<2 ? e.string : strrchr(e.string,'.'))[0]  =  0;
1050                }
1051		break;
1052
1053	      case 3: /* -r -rev */
1054		if (expandsym(ptr->endrev, &e)) {
1055		    if ((n = countnumflds(e.string)) < 2)
1056			bufscpy(&s, ".1");
1057		    else {
1058			bufscpy(&s, e.string);
1059			VOID strcpy(strrchr(s.string,'.'), ".1");
1060		    }
1061                }
1062		break;
1063
1064	      default: /* -r rev1-rev2 */
1065		if (
1066			expandsym(ptr->strtrev, &s)
1067		    &&	expandsym(ptr->endrev, &e)
1068		    &&	checkrevpair(s.string, e.string)
1069		) {
1070		    n = countnumflds(s.string);
1071		    /* Swap if out of order.  */
1072		    if (compartial(s.string,e.string,n) > 0) {
1073			rstart = &e;
1074			rend = &s;
1075		    }
1076		}
1077		break;
1078	    }
1079
1080	    if (n) {
1081		pt = ftalloc(struct Revpairs);
1082		pt->numfld = n;
1083		pt->strtrev = fstr_save(rstart->string);
1084		pt->endrev = fstr_save(rend->string);
1085                pt->rnext = Revlst;
1086                Revlst = pt;
1087	    }
1088	    ptr = ptr->rnext;
1089        }
1090        /* Now take care of branchflag */
1091	if (branchflag && (Dbranch||Head)) {
1092	    pt = ftalloc(struct Revpairs);
1093	    pt->strtrev = pt->endrev =
1094		Dbranch ? Dbranch : fstr_save(partialno(&s,Head->num,1));
1095	    pt->rnext=Revlst; Revlst=pt;
1096	    pt->numfld = countnumflds(pt->strtrev);
1097        }
1098	bufautoend(&s);
1099	bufautoend(&e);
1100}
1101
1102
1103
1104	static int
1105checkrevpair(num1,num2)
1106	char const *num1, *num2;
1107/*  function:  check whether num1, num2 are legal pair,i.e.
1108    only the last field are different and have same number of
1109    fields( if length <= 2, may be different if first field)   */
1110
1111{
1112	unsigned length = countnumflds(num1);
1113
1114	if (
1115			countnumflds(num2) != length
1116		||	2 < length  &&  compartial(num1, num2, length-1) != 0
1117	) {
1118	    error("invalid branch or revision pair %s : %s", num1, num2);
1119            return false;
1120        }
1121
1122        return true;
1123}
1124
1125
1126
1127	static void
1128getrevpairs(argv)
1129register     char    * argv;
1130/*  function:  get revision or branch range from command line, and   */
1131/*             store in revlist                                      */
1132
1133{
1134        register    char    c;
1135        struct      Revpairs  * nextrevpair;
1136	int separator;
1137
1138	c = *argv;
1139
1140	/* Support old ambiguous '-' syntax; this will go away.  */
1141	if (strchr(argv,':'))
1142	    separator = ':';
1143	else {
1144	    if (strchr(argv,'-')  &&  VERSION(5) <= RCSversion)
1145		warn("`-' is obsolete in `-r%s'; use `:' instead", argv);
1146	    separator = '-';
1147	}
1148
1149	for (;;) {
1150	    while (c==' ' || c=='\t' || c=='\n')
1151		c = *++argv;
1152	    nextrevpair = talloc(struct Revpairs);
1153            nextrevpair->rnext = revlist;
1154            revlist = nextrevpair;
1155	    nextrevpair->numfld = 1;
1156	    nextrevpair->strtrev = argv;
1157	    for (;;  c = *++argv) {
1158		switch (c) {
1159		    default:
1160			continue;
1161		    case '\0': case ' ': case '\t': case '\n':
1162		    case ',': case ';':
1163			break;
1164		    case ':': case '-':
1165			if (c == separator)
1166			    break;
1167			continue;
1168		}
1169		break;
1170	    }
1171	    *argv = '\0';
1172	    while (c==' ' || c=='\t' || c=='\n')
1173		c = *++argv;
1174	    if (c == separator) {
1175                while( (c =(*++argv)) == ' ' || c == '\t' || c =='\n') ;
1176		nextrevpair->endrev = argv;
1177		for (;;  c = *++argv) {
1178		    switch (c) {
1179			default:
1180			    continue;
1181			case '\0': case ' ': case '\t': case '\n':
1182			case ',': case ';':
1183			    break;
1184			case ':': case '-':
1185			    if (c == separator)
1186				continue;
1187			    break;
1188		    }
1189		    break;
1190		}
1191		*argv = '\0';
1192		while (c==' ' || c=='\t' || c =='\n')
1193		    c = *++argv;
1194		nextrevpair->numfld =
1195		    !nextrevpair->endrev[0] ? 2 /* -rrev- */ :
1196		    !nextrevpair->strtrev[0] ? 3 /* -r-rev */ :
1197		    4 /* -rrev1-rev2 */;
1198            }
1199	    if (!c)
1200		break;
1201	    if (c!=',' && c!=';')
1202		error("missing `,' near `%c%s'", c, argv+1);
1203	}
1204}
1205