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