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