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