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