Initial import from FreeBSD RELENG_4:
[dragonfly.git] / gnu / usr.bin / rcs / rlog / rlog.c
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
7 This file is part of RCS.
8
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 Report 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
160 struct rcslockers {                   /* lockers in locker option; stored   */
161      char const         * login;      /* lockerlist                         */
162      struct rcslockers  * lockerlink;
163      }  ;
164
165 struct  stateattri {                  /* states in state option; stored in  */
166      char const         * status;     /* statelist                          */
167      struct  stateattri * nextstate;
168      }  ;
169
170 struct  authors {                     /* login names in author option;      */
171      char const         * login;      /* stored in authorlist               */
172      struct     authors * nextauthor;
173      }  ;
174
175 struct 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
182 struct 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
189 static char extractdelta P((struct hshentry const*));
190 static int checkrevpair P((char const*,char const*));
191 static int extdate P((struct hshentry*));
192 static int getnumericrev P((void));
193 static struct hshentry const *readdeltalog P((void));
194 static void cleanup P((void));
195 static void exttree P((struct hshentry*));
196 static void getauthor P((char*));
197 static void getdatepair P((char*));
198 static void getlocker P((char*));
199 static void getrevpairs P((char*));
200 static void getscript P((struct hshentry*));
201 static void getstate P((char*));
202 static void putabranch P((struct hshentry const*));
203 static void putadelta P((struct hshentry const*,struct hshentry const*,int));
204 static void putforest P((struct branchhead const*));
205 static void putree P((struct hshentry const*));
206 static void putrunk P((void));
207 static void recentdate P((struct hshentry const*,struct Datepairs*));
208 static void trunclocks P((void));
209
210 static char const *insDelFormat;
211 static int branchflag;  /*set on -b */
212 static int exitstatus;
213 static int lockflag;
214 static struct Datepairs *datelist, *duelst;
215 static struct Revpairs *revlist, *Revlst;
216 static struct authors *authorlist;
217 static struct rcslockers *lockerlist;
218 static struct stateattri *statelist;
219
220
221 mainProg(rlogId, "rlog", "$FreeBSD: src/gnu/usr.bin/rcs/rlog/rlog.c,v 1.13 1999/08/27 23:36:59 peter Exp $")
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
486 cleanup()
487 {
488         if (nerror) exitstatus = EXIT_FAILURE;
489         Izclose(&finptr);
490 }
491
492 #if RCS_lint
493 #       define exiterr rlogExit
494 #endif
495         void
496 exiterr()
497 {
498         _exit(EXIT_FAILURE);
499 }
500
501
502
503         static void
504 putrunk()
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
517 putree(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
534 putforest(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
550 putabranch(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
567 putadelta(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 *
633 readdeltalog()
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
668 getscript(Delta)
669 struct    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
725 exttree(root)
726 struct 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
749 getlocker(argv)
750 char    * 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
782 getauthor(argv)
783 char   *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
819 getstate(argv)
820 char   * 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
853 trunclocks()
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
878 recentdate(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
909 extdate(root)
910 struct  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
962 extractdelta(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
1006 getdatepair(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
1091 getnumericrev()
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
1188 checkrevpair(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
1211 getrevpairs(argv)
1212 register     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 }