xref: /dragonfly/contrib/cvs-1.12/src/log.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  *
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  *
13  * Print Log Information
14  *
15  * Prints the RCS "log" (rlog) information for the specified files.  With no
16  * argument, prints the log information for all the files in the directory
17  * (recursive by default).
18  */
19 
20 #include "cvs.h"
21 #include <assert.h>
22 
23 /* This structure holds information parsed from the -r option.  */
24 
25 struct option_revlist
26 {
27     /* The next -r option.  */
28     struct option_revlist *next;
29     /* The first revision to print.  This is NULL if the range is
30        :rev, or if no revision is given.  */
31     char *first;
32     /* The last revision to print.  This is NULL if the range is rev:,
33        or if no revision is given.  If there is no colon, first and
34        last are the same.  */
35     char *last;
36     /* Nonzero if there was a trailing `.', which means to print only
37        the head revision of a branch.  */
38     int branchhead;
39     /* Nonzero if first and last are inclusive.  */
40     int inclusive;
41 };
42 
43 /* This structure holds information derived from option_revlist given
44    a particular RCS file.  */
45 
46 struct revlist
47 {
48     /* The next pair.  */
49     struct revlist *next;
50     /* The first numeric revision to print.  */
51     char *first;
52     /* The last numeric revision to print.  */
53     char *last;
54     /* The number of fields in these revisions (one more than
55        numdots).  */
56     int fields;
57     /* Whether first & last are to be included or excluded.  */
58     int inclusive;
59 };
60 
61 /* This structure holds information parsed from the -d option.  */
62 
63 struct datelist
64 {
65     /* The next date.  */
66     struct datelist *next;
67     /* The starting date.  */
68     char *start;
69     /* The ending date.  */
70     char *end;
71     /* Nonzero if the range is inclusive rather than exclusive.  */
72     int inclusive;
73 };
74 
75 /* This structure is used to pass information through start_recursion.  */
76 struct log_data
77 {
78     /* Nonzero if the -R option was given, meaning that only the name
79        of the RCS file should be printed.  */
80     int nameonly;
81     /* Nonzero if the -h option was given, meaning that only header
82        information should be printed.  */
83     int header;
84     /* Nonzero if the -t option was given, meaning that only the
85        header and the descriptive text should be printed.  */
86     int long_header;
87     /* Nonzero if the -N option was seen, meaning that tag information
88        should not be printed.  */
89     int notags;
90     /* Nonzero if the -b option was seen, meaning that only revisions
91        on the default branch should be printed.  */
92     int default_branch;
93     /* Nonzero if the -S option was seen, meaning that the header/name
94        should be suppressed if no revisions are selected.  */
95     int sup_header;
96     /* If not NULL, the value given for the -r option, which lists
97        sets of revisions to be printed.  */
98     struct option_revlist *revlist;
99     /* If not NULL, the date pairs given for the -d option, which
100        select date ranges to print.  */
101     struct datelist *datelist;
102     /* If not NULL, the single dates given for the -d option, which
103        select specific revisions to print based on a date.  */
104     struct datelist *singledatelist;
105     /* If not NULL, the list of states given for the -s option, which
106        only prints revisions of given states.  */
107     List *statelist;
108     /* If not NULL, the list of login names given for the -w option,
109        which only prints revisions checked in by given users.  */
110     List *authorlist;
111 };
112 
113 /* This structure is used to pass information through walklist.  */
114 struct log_data_and_rcs
115 {
116     struct log_data *log_data;
117     struct revlist *revlist;
118     RCSNode *rcs;
119 };
120 
121 static int rlog_proc (int argc, char **argv, char *xwhere,
122                       char *mwhere, char *mfile, int shorten,
123                       int local_specified, char *mname, char *msg);
124 static Dtype log_dirproc (void *callerdat, const char *dir,
125                           const char *repository, const char *update_dir,
126                           List *entries);
127 static int log_fileproc (void *callerdat, struct file_info *finfo);
128 static struct option_revlist *log_parse_revlist (const char *);
129 static void log_parse_date (struct log_data *, const char *);
130 static void log_parse_list (List **, const char *);
131 static struct revlist *log_expand_revlist (RCSNode *, char *,
132                                            struct option_revlist *, int);
133 static void log_free_revlist (struct revlist *);
134 static int log_version_requested (struct log_data *, struct revlist *,
135                                                    RCSNode *, RCSVers *);
136 static int log_symbol (Node *, void *);
137 static int log_count (Node *, void *);
138 static int log_fix_singledate (Node *, void *);
139 static int log_count_print (Node *, void *);
140 static void log_tree (struct log_data *, struct revlist *,
141                                    RCSNode *, const char *);
142 static void log_abranch (struct log_data *, struct revlist *,
143                                         RCSNode *, const char *);
144 static void log_version (struct log_data *, struct revlist *,
145                                         RCSNode *, RCSVers *, int);
146 static int log_branch (Node *, void *);
147 static int version_compare (const char *, const char *, int);
148 
149 static struct log_data log_data;
150 static int is_rlog;
151 
152 static const char *const log_usage[] =
153 {
154     "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
155     "    [-w[logins]] [files...]\n",
156     "\t-l\tLocal directory only, no recursion.\n",
157     "\t-b\tOnly list revisions on the default branch.\n",
158     "\t-h\tOnly print header.\n",
159     "\t-R\tOnly print name of RCS file.\n",
160     "\t-t\tOnly print header and descriptive text.\n",
161     "\t-N\tDo not list tags.\n",
162     "\t-S\tDo not print name/header if no revisions selected.  -d, -r,\n",
163     "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
164     "\t\t-t without this option.\n",
165     "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
166     "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
167     "\t   rev1::rev2  Between rev1 and rev2, excluding rev1.\n",
168     "\t   rev:        rev and following revisions on the same branch.\n",
169     "\t   rev::       After rev on the same branch.\n",
170     "\t   :rev        rev and previous revisions on the same branch.\n",
171     "\t   ::rev       rev and previous revisions on the same branch.\n",
172     "\t   rev         Just rev.\n",
173     "\t   branch      All revisions on the branch.\n",
174     "\t   branch.     The last revision on the branch.\n",
175     "\t-d dates\tA semicolon-separated list of dates\n",
176     "\t        \t(D1<D2 for range, D for latest before).\n",
177     "\t-s states\tOnly list revisions with specified states.\n",
178     "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
179     "(Specify the --help global option for a list of other help options)\n",
180     NULL
181 };
182 
183 #ifdef CLIENT_SUPPORT
184 
185 
186 
187 /* Helper function for send_arg_list.  */
188 static int
send_one(Node * node,void * closure)189 send_one (Node *node, void *closure)
190 {
191     char *option = closure;
192 
193     send_to_server ("Argument ", 0);
194     send_to_server (option, 0);
195     if (strcmp (node->key, "@@MYSELF") == 0)
196           /* It is a bare -w option.  Note that we must send it as
197              -w rather than messing with getcaller() or something (which on
198              the client will return garbage).  */
199           ;
200     else
201           send_to_server (node->key, 0);
202     send_to_server ("\012", 0);
203     return 0;
204 }
205 
206 
207 
208 /* For each element in ARG, send an argument consisting of OPTION
209    concatenated with that element.  */
210 static void
send_arg_list(char * option,List * arg)211 send_arg_list (char *option, List *arg)
212 {
213     if (arg == NULL)
214           return;
215     walklist (arg, send_one, option);
216 }
217 
218 #endif
219 
220 
221 
222 int
cvslog(int argc,char ** argv)223 cvslog (int argc, char **argv)
224 {
225     int c;
226     int err = 0;
227     int local = 0;
228     struct option_revlist **prl;
229 
230     is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
231 
232     if (argc == -1)
233           usage (log_usage);
234 
235     memset (&log_data, 0, sizeof log_data);
236     prl = &log_data.revlist;
237 
238     optind = 0;
239     while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::")) != -1)
240     {
241           switch (c)
242           {
243               case 'b':
244                     log_data.default_branch = 1;
245                     break;
246               case 'd':
247                     log_parse_date (&log_data, optarg);
248                     break;
249               case 'h':
250                     log_data.header = 1;
251                     break;
252               case 'l':
253                     local = 1;
254                     break;
255               case 'N':
256                     log_data.notags = 1;
257                     break;
258               case 'S':
259                     log_data.sup_header = 1;
260                     break;
261               case 'R':
262                     log_data.nameonly = 1;
263                     break;
264               case 'r':
265                     *prl = log_parse_revlist (optarg);
266                     prl = &(*prl)->next;
267                     break;
268               case 's':
269                     log_parse_list (&log_data.statelist, optarg);
270                     break;
271               case 't':
272                     log_data.long_header = 1;
273                     break;
274               case 'w':
275                     if (optarg != NULL)
276                         log_parse_list (&log_data.authorlist, optarg);
277                     else
278                         log_parse_list (&log_data.authorlist, "@@MYSELF");
279                     break;
280               case '?':
281               default:
282                     usage (log_usage);
283                     break;
284           }
285     }
286     argc -= optind;
287     argv += optind;
288 
289     wrap_setup ();
290 
291 #ifdef CLIENT_SUPPORT
292     if (current_parsed_root->isremote)
293     {
294           struct datelist *p;
295           struct option_revlist *rp;
296           char datetmp[MAXDATELEN];
297 
298           /* We're the local client.  Fire up the remote server.  */
299           start_server ();
300 
301           if (is_rlog && !supported_request ("rlog"))
302               error (1, 0, "server does not support rlog");
303 
304           ign_setup ();
305 
306           if (log_data.default_branch)
307               send_arg ("-b");
308 
309           while (log_data.datelist != NULL)
310           {
311               p = log_data.datelist;
312               log_data.datelist = p->next;
313               send_to_server ("Argument -d\012", 0);
314               send_to_server ("Argument ", 0);
315               date_to_internet (datetmp, p->start);
316               send_to_server (datetmp, 0);
317               if (p->inclusive)
318                     send_to_server ("<=", 0);
319               else
320                     send_to_server ("<", 0);
321               date_to_internet (datetmp, p->end);
322               send_to_server (datetmp, 0);
323               send_to_server ("\012", 0);
324               if (p->start)
325                     free (p->start);
326               if (p->end)
327                     free (p->end);
328               free (p);
329           }
330           while (log_data.singledatelist != NULL)
331           {
332               p = log_data.singledatelist;
333               log_data.singledatelist = p->next;
334               send_to_server ("Argument -d\012", 0);
335               send_to_server ("Argument ", 0);
336               date_to_internet (datetmp, p->end);
337               send_to_server (datetmp, 0);
338               send_to_server ("\012", 0);
339               if (p->end)
340                     free (p->end);
341               free (p);
342           }
343 
344           if (log_data.header)
345               send_arg ("-h");
346           if (local)
347               send_arg("-l");
348           if (log_data.notags)
349               send_arg("-N");
350           if (log_data.sup_header)
351               send_arg("-S");
352           if (log_data.nameonly)
353               send_arg("-R");
354           if (log_data.long_header)
355               send_arg("-t");
356 
357           while (log_data.revlist != NULL)
358           {
359               rp = log_data.revlist;
360               log_data.revlist = rp->next;
361               send_to_server ("Argument -r", 0);
362               if (rp->branchhead)
363               {
364                     if (rp->first != NULL)
365                         send_to_server (rp->first, 0);
366                     send_to_server (".", 1);
367               }
368               else
369               {
370                     if (rp->first != NULL)
371                         send_to_server (rp->first, 0);
372                     send_to_server (":", 1);
373                     if (!rp->inclusive)
374                         send_to_server (":", 1);
375                     if (rp->last != NULL)
376                         send_to_server (rp->last, 0);
377               }
378               send_to_server ("\012", 0);
379               if (rp->first)
380                     free (rp->first);
381               if (rp->last)
382                     free (rp->last);
383               free (rp);
384           }
385           send_arg_list ("-s", log_data.statelist);
386           dellist (&log_data.statelist);
387           send_arg_list ("-w", log_data.authorlist);
388           dellist (&log_data.authorlist);
389           send_arg ("--");
390 
391           if (is_rlog)
392           {
393               int i;
394               for (i = 0; i < argc; i++)
395                     send_arg (argv[i]);
396               send_to_server ("rlog\012", 0);
397           }
398           else
399           {
400               send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
401               send_file_names (argc, argv, SEND_EXPAND_WILD);
402               send_to_server ("log\012", 0);
403           }
404         err = get_responses_and_close ();
405           return err;
406     }
407 #endif
408 
409     /* OK, now that we know we are local/server, we can resolve @@MYSELF
410        into our user name.  */
411     if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
412           log_parse_list (&log_data.authorlist, getcaller ());
413 
414     if (is_rlog)
415     {
416           DBM *db;
417           int i;
418           db = open_module ();
419           for (i = 0; i < argc; i++)
420           {
421              err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
422                                NULL, 0, local, 0, 0, NULL);
423           }
424           close_module (db);
425     }
426     else
427     {
428         err = rlog_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, local, NULL,
429                          NULL);
430     }
431 
432     while (log_data.revlist)
433     {
434           struct option_revlist *rl = log_data.revlist->next;
435           if (log_data.revlist->first)
436               free (log_data.revlist->first);
437           if (log_data.revlist->last)
438               free (log_data.revlist->last);
439           free (log_data.revlist);
440           log_data.revlist = rl;
441     }
442     while (log_data.datelist)
443     {
444           struct datelist *nd = log_data.datelist->next;
445           if (log_data.datelist->start)
446               free (log_data.datelist->start);
447           if (log_data.datelist->end)
448               free (log_data.datelist->end);
449           free (log_data.datelist);
450           log_data.datelist = nd;
451     }
452     while (log_data.singledatelist)
453     {
454           struct datelist *nd = log_data.singledatelist->next;
455           if (log_data.singledatelist->start)
456               free (log_data.singledatelist->start);
457           if (log_data.singledatelist->end)
458               free (log_data.singledatelist->end);
459           free (log_data.singledatelist);
460           log_data.singledatelist = nd;
461     }
462     dellist (&log_data.statelist);
463     dellist (&log_data.authorlist);
464 
465     return err;
466 }
467 
468 
469 
470 static int
rlog_proc(int argc,char ** argv,char * xwhere,char * mwhere,char * mfile,int shorten,int local,char * mname,char * msg)471 rlog_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile,
472            int shorten, int local, char *mname, char *msg)
473 {
474     /* Begin section which is identical to patch_proc--should this
475        be abstracted out somehow?  */
476     char *myargv[2];
477     int err = 0;
478     int which;
479     char *repository = NULL;
480     char *where;
481 
482     if (is_rlog)
483     {
484           repository = xmalloc (strlen (current_parsed_root->directory)
485                               + strlen (argv[0])
486                                     + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
487           (void)sprintf (repository, "%s/%s",
488                        current_parsed_root->directory, argv[0]);
489           where = xmalloc (strlen (argv[0])
490                          + (mfile == NULL ? 0 : strlen (mfile) + 1)
491                                + 1);
492           (void)strcpy (where, argv[0]);
493 
494           /* If mfile isn't null, we need to set up to do only part of theu
495          * module.
496          */
497           if (mfile != NULL)
498           {
499               char *cp;
500               char *path;
501 
502               /* If the portion of the module is a path, put the dir part on
503              * repos.
504              */
505               if ((cp = strrchr (mfile, '/')) != NULL)
506               {
507                     *cp = '\0';
508                     (void)strcat (repository, "/");
509                     (void)strcat (repository, mfile);
510                     (void)strcat (where, "/");
511                     (void)strcat (where, mfile);
512                     mfile = cp + 1;
513               }
514 
515               /* take care of the rest */
516               path = Xasprintf ("%s/%s", repository, mfile);
517               if (isdir (path))
518               {
519                     /* directory means repository gets the dir tacked on */
520                     (void)strcpy (repository, path);
521                     (void)strcat (where, "/");
522                     (void)strcat (where, mfile);
523               }
524               else
525               {
526                     myargv[0] = argv[0];
527                     myargv[1] = mfile;
528                     argc = 2;
529                     argv = myargv;
530               }
531               free (path);
532           }
533 
534           /* cd to the starting repository */
535           if (CVS_CHDIR (repository) < 0)
536           {
537               error (0, errno, "cannot chdir to %s", repository);
538               free (repository);
539               free (where);
540               return 1;
541           }
542           /* End section which is identical to patch_proc.  */
543 
544           which = W_REPOS | W_ATTIC;
545     }
546     else
547     {
548         repository = NULL;
549         where = NULL;
550         which = W_LOCAL | W_REPOS | W_ATTIC;
551     }
552 
553     err = start_recursion (log_fileproc, NULL, log_dirproc,
554                                  NULL, &log_data,
555                                  argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
556                                  where, 1, repository);
557 
558     if (!(which & W_LOCAL)) free (repository);
559     if (where) free (where);
560 
561     return err;
562 }
563 
564 
565 
566 /*
567  * Parse a revision list specification.
568  */
569 static struct option_revlist *
log_parse_revlist(const char * argstring)570 log_parse_revlist (const char *argstring)
571 {
572     char *orig_copy, *copy;
573     struct option_revlist *ret, **pr;
574 
575     /* Unfortunately, rlog accepts -r without an argument to mean that
576        latest revision on the default branch, so we must support that
577        for compatibility.  */
578     if (argstring == NULL)
579           argstring = "";
580 
581     ret = NULL;
582     pr = &ret;
583 
584     /* Copy the argument into memory so that we can change it.  We
585        don't want to change the argument because, at least as of this
586        writing, we will use it if we send the arguments to the server.  */
587     orig_copy = copy = xstrdup (argstring);
588     while (copy != NULL)
589     {
590           char *comma;
591           struct option_revlist *r;
592 
593           comma = strchr (copy, ',');
594           if (comma != NULL)
595               *comma++ = '\0';
596 
597           r = xmalloc (sizeof *r);
598           r->next = NULL;
599           r->first = copy;
600           r->branchhead = 0;
601           r->last = strchr (copy, ':');
602           if (r->last != NULL)
603           {
604               *r->last++ = '\0';
605               r->inclusive = (*r->last != ':');
606               if (!r->inclusive)
607                     r->last++;
608           }
609           else
610           {
611               r->last = r->first;
612               r->inclusive = 1;
613               if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
614               {
615                     r->branchhead = 1;
616                     r->first[strlen (r->first) - 1] = '\0';
617               }
618           }
619 
620           if (*r->first == '\0')
621               r->first = NULL;
622           if (*r->last == '\0')
623               r->last = NULL;
624 
625           if (r->first != NULL)
626               r->first = xstrdup (r->first);
627           if (r->last != NULL)
628               r->last = xstrdup (r->last);
629 
630           *pr = r;
631           pr = &r->next;
632 
633           copy = comma;
634     }
635 
636     free (orig_copy);
637     return ret;
638 }
639 
640 
641 
642 /*
643  * Parse a date specification.
644  */
645 static void
log_parse_date(struct log_data * log_data,const char * argstring)646 log_parse_date (struct log_data *log_data, const char *argstring)
647 {
648     char *orig_copy, *copy;
649 
650     /* Copy the argument into memory so that we can change it.  We
651        don't want to change the argument because, at least as of this
652        writing, we will use it if we send the arguments to the server.  */
653     orig_copy = copy = xstrdup (argstring);
654     while (copy != NULL)
655     {
656           struct datelist *nd, **pd;
657           char *cpend, *cp, *ds, *de;
658 
659           nd = xmalloc (sizeof *nd);
660 
661           cpend = strchr (copy, ';');
662           if (cpend != NULL)
663               *cpend++ = '\0';
664 
665           pd = &log_data->datelist;
666           nd->inclusive = 0;
667 
668           if ((cp = strchr (copy, '>')) != NULL)
669           {
670               *cp++ = '\0';
671               if (*cp == '=')
672               {
673                     ++cp;
674                     nd->inclusive = 1;
675               }
676               ds = cp;
677               de = copy;
678           }
679           else if ((cp = strchr (copy, '<')) != NULL)
680           {
681               *cp++ = '\0';
682               if (*cp == '=')
683               {
684                     ++cp;
685                     nd->inclusive = 1;
686               }
687               ds = copy;
688               de = cp;
689           }
690           else
691           {
692               ds = NULL;
693               de = copy;
694               pd = &log_data->singledatelist;
695           }
696 
697           if (ds == NULL)
698               nd->start = NULL;
699           else if (*ds != '\0')
700               nd->start = Make_Date (ds);
701           else
702           {
703             /* 1970 was the beginning of time, as far as get_date and
704                Make_Date are concerned.  FIXME: That is true only if time_t
705                is a POSIX-style time and there is nothing in ANSI that
706                mandates that.  It would be cleaner to set a flag saying
707                whether or not there is a start date.  */
708               nd->start = Make_Date ("1/1/1970 UTC");
709           }
710 
711           if (*de != '\0')
712               nd->end = Make_Date (de);
713           else
714           {
715               /* We want to set the end date to some time sufficiently far
716                  in the future to pick up all revisions that have been
717                  created since the specified date and the time `cvs log'
718                  completes.  FIXME: The date in question only makes sense
719                  if time_t is a POSIX-style time and it is 32 bits
720                  and signed.  We should instead be setting a flag saying
721                  whether or not there is an end date.  Note that using
722                  something like "next week" would break the testsuite (and,
723                  perhaps less importantly, loses if the clock is set grossly
724                  wrong).  */
725               nd->end = Make_Date ("2038-01-01");
726           }
727 
728           nd->next = *pd;
729           *pd = nd;
730 
731           copy = cpend;
732     }
733 
734     free (orig_copy);
735 }
736 
737 
738 
739 /*
740  * Parse a comma separated list of items, and add each one to *PLIST.
741  */
742 static void
log_parse_list(List ** plist,const char * argstring)743 log_parse_list (List **plist, const char *argstring)
744 {
745     while (1)
746     {
747           Node *p;
748           char *cp;
749 
750           p = getnode ();
751 
752           cp = strchr (argstring, ',');
753           if (cp == NULL)
754               p->key = xstrdup (argstring);
755           else
756           {
757               size_t len;
758 
759               len = cp - argstring;
760               p->key = xmalloc (len + 1);
761               strncpy (p->key, argstring, len);
762               p->key[len] = '\0';
763           }
764 
765           if (*plist == NULL)
766               *plist = getlist ();
767           if (addnode (*plist, p) != 0)
768               freenode (p);
769 
770           if (cp == NULL)
771               break;
772 
773           argstring = cp + 1;
774     }
775 }
776 
777 
778 
779 static int
printlock_proc(Node * lock,void * foo)780 printlock_proc (Node *lock, void *foo)
781 {
782     cvs_output ("\n\t", 2);
783     cvs_output (lock->data, 0);
784     cvs_output (": ", 2);
785     cvs_output (lock->key, 0);
786     return 0;
787 }
788 
789 
790 
791 /*
792  * Do an rlog on a file
793  */
794 static int
log_fileproc(void * callerdat,struct file_info * finfo)795 log_fileproc (void *callerdat, struct file_info *finfo)
796 {
797     struct log_data *log_data = callerdat;
798     Node *p;
799     char *baserev;
800     int selrev = -1;
801     RCSNode *rcsfile;
802     char buf[50];
803     struct revlist *revlist = NULL;
804     struct log_data_and_rcs log_data_and_rcs;
805 
806     rcsfile = finfo->rcs;
807     p = findnode (finfo->entries, finfo->file);
808     if (p != NULL)
809     {
810           Entnode *e = p->data;
811           baserev = e->version;
812           if (baserev[0] == '-') ++baserev;
813     }
814     else
815           baserev = NULL;
816 
817     if (rcsfile == NULL)
818     {
819           /* no rcs file.  What *do* we know about this file? */
820           if (baserev != NULL)
821           {
822               if (baserev[0] == '0' && baserev[1] == '\0')
823               {
824                     if (!really_quiet)
825                         error (0, 0, "%s has been added, but not committed",
826                                  finfo->file);
827                     return 0;
828               }
829           }
830 
831           if (!really_quiet)
832               error (0, 0, "nothing known about %s", finfo->file);
833 
834           return 1;
835     }
836 
837     if (log_data->sup_header || !log_data->nameonly)
838     {
839 
840           /* We will need all the information in the RCS file.  */
841           RCS_fully_parse (rcsfile);
842 
843           /* Turn any symbolic revisions in the revision list into numeric
844              revisions.  */
845           revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist,
846                                               log_data->default_branch);
847           if (log_data->sup_header
848             || (!log_data->header && !log_data->long_header))
849           {
850               log_data_and_rcs.log_data = log_data;
851               log_data_and_rcs.revlist = revlist;
852               log_data_and_rcs.rcs = rcsfile;
853 
854               /* If any single dates were specified, we need to identify the
855                  revisions they select.  Each one selects the single
856                  revision, which is otherwise selected, of that date or
857                  earlier.  The log_fix_singledate routine will fill in the
858                  start date for each specific revision.  */
859               if (log_data->singledatelist != NULL)
860                     walklist (rcsfile->versions, log_fix_singledate,
861                                 &log_data_and_rcs);
862 
863               selrev = walklist (rcsfile->versions, log_count_print,
864                                      &log_data_and_rcs);
865               if (log_data->sup_header && selrev == 0)
866               {
867                     log_free_revlist (revlist);
868                     return 0;
869               }
870           }
871 
872     }
873 
874     if (log_data->nameonly)
875     {
876           cvs_output (rcsfile->print_path, 0);
877           cvs_output ("\n", 1);
878           log_free_revlist (revlist);
879           return 0;
880     }
881 
882     /* The output here is intended to be exactly compatible with the
883        output of rlog.  I'm not sure whether this code should be here
884        or in rcs.c; I put it here because it is specific to the log
885        function, even though it uses information gathered by the
886        functions in rcs.c.  */
887 
888     cvs_output ("\n", 1);
889 
890     cvs_output ("RCS file: ", 0);
891     cvs_output (rcsfile->print_path, 0);
892 
893     if (!is_rlog)
894     {
895           cvs_output ("\nWorking file: ", 0);
896           if (finfo->update_dir[0] != '\0')
897           {
898               cvs_output (finfo->update_dir, 0);
899               cvs_output ("/", 0);
900           }
901           cvs_output (finfo->file, 0);
902     }
903 
904     cvs_output ("\nhead:", 0);
905     if (rcsfile->head != NULL)
906     {
907           cvs_output (" ", 1);
908           cvs_output (rcsfile->head, 0);
909     }
910 
911     cvs_output ("\nbranch:", 0);
912     if (rcsfile->branch != NULL)
913     {
914           cvs_output (" ", 1);
915           cvs_output (rcsfile->branch, 0);
916     }
917 
918     cvs_output ("\nlocks:", 0);
919     if (rcsfile->strict_locks)
920           cvs_output (" strict", 0);
921     walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
922 
923     cvs_output ("\naccess list:", 0);
924     if (rcsfile->access != NULL)
925     {
926           const char *cp;
927 
928           cp = rcsfile->access;
929           while (*cp != '\0')
930           {
931                     const char *cp2;
932 
933                     cvs_output ("\n\t", 2);
934                     cp2 = cp;
935                     while (!isspace ((unsigned char)*cp2) && *cp2 != '\0')
936                         ++cp2;
937                     cvs_output (cp, cp2 - cp);
938                     cp = cp2;
939                     while (isspace ((unsigned char)*cp) && *cp != '\0')
940                         ++cp;
941           }
942     }
943 
944     if (!log_data->notags)
945     {
946           List *syms;
947 
948           cvs_output ("\nsymbolic names:", 0);
949           syms = RCS_symbols (rcsfile);
950           walklist (syms, log_symbol, NULL);
951     }
952 
953     cvs_output ("\nkeyword substitution: ", 0);
954     if (rcsfile->expand == NULL)
955           cvs_output ("kv", 2);
956     else
957           cvs_output (rcsfile->expand, 0);
958 
959     cvs_output ("\ntotal revisions: ", 0);
960     sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
961     cvs_output (buf, 0);
962 
963     if (selrev >= 0)
964     {
965           cvs_output (";\tselected revisions: ", 0);
966           sprintf (buf, "%d", selrev);
967           cvs_output (buf, 0);
968     }
969 
970     cvs_output ("\n", 1);
971 
972     if (!log_data->header || log_data->long_header)
973     {
974           cvs_output ("description:\n", 0);
975           if (rcsfile->desc != NULL)
976               cvs_output (rcsfile->desc, 0);
977     }
978 
979     if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
980     {
981           p = findnode (rcsfile->versions, rcsfile->head);
982           if (p == NULL)
983               error (1, 0, "can not find head revision in `%s'",
984                        finfo->fullname);
985           while (p != NULL)
986           {
987               RCSVers *vers = p->data;
988 
989               log_version (log_data, revlist, rcsfile, vers, 1);
990               if (vers->next == NULL)
991                     p = NULL;
992               else
993               {
994                     p = findnode (rcsfile->versions, vers->next);
995                     if (p == NULL)
996                         error (1, 0, "can not find next revision `%s' in `%s'",
997                                  vers->next, finfo->fullname);
998               }
999           }
1000 
1001           log_tree (log_data, revlist, rcsfile, rcsfile->head);
1002     }
1003 
1004     cvs_output("\
1005 =============================================================================\n",
1006                  0);
1007 
1008     /* Free up the new revlist and restore the old one.  */
1009     log_free_revlist (revlist);
1010 
1011     /* If singledatelist is not NULL, free up the start dates we added
1012        to it.  */
1013     if (log_data->singledatelist != NULL)
1014     {
1015           struct datelist *d;
1016 
1017           for (d = log_data->singledatelist; d != NULL; d = d->next)
1018           {
1019               if (d->start != NULL)
1020                     free (d->start);
1021               d->start = NULL;
1022           }
1023     }
1024 
1025     return 0;
1026 }
1027 
1028 
1029 
1030 /*
1031  * Fix up a revision list in order to compare it against versions.
1032  * Expand any symbolic revisions.
1033  */
1034 static struct revlist *
log_expand_revlist(RCSNode * rcs,char * baserev,struct option_revlist * revlist,int default_branch)1035 log_expand_revlist (RCSNode *rcs, char *baserev,
1036                     struct option_revlist *revlist, int default_branch)
1037 {
1038     struct option_revlist *r;
1039     struct revlist *ret, **pr;
1040 
1041     ret = NULL;
1042     pr = &ret;
1043     for (r = revlist; r != NULL; r = r->next)
1044     {
1045           struct revlist *nr;
1046 
1047           nr = xmalloc (sizeof *nr);
1048           nr->inclusive = r->inclusive;
1049 
1050           if (r->first == NULL && r->last == NULL)
1051           {
1052               /* If both first and last are NULL, it means that we want
1053                  just the head of the default branch, which is RCS_head.  */
1054               nr->first = RCS_head (rcs);
1055               if (!nr->first)
1056               {
1057                     if (!really_quiet)
1058                         error (0, 0, "No head revision in archive `%s'.",
1059                                rcs->path);
1060                     nr->last = NULL;
1061                     nr->fields = 0;
1062               }
1063               else
1064               {
1065                     nr->last = xstrdup (nr->first);
1066                     nr->fields = numdots (nr->first) + 1;
1067               }
1068           }
1069           else if (r->branchhead)
1070           {
1071               char *branch;
1072 
1073               /* Print just the head of the branch.  */
1074               if (isdigit ((unsigned char) r->first[0]))
1075                     nr->first = RCS_getbranch (rcs, r->first, 1);
1076               else
1077               {
1078                     branch = RCS_whatbranch (rcs, r->first);
1079                     if (branch == NULL)
1080                         nr->first = NULL;
1081                     else
1082                     {
1083                         nr->first = RCS_getbranch (rcs, branch, 1);
1084                         free (branch);
1085                     }
1086               }
1087               if (!nr->first)
1088               {
1089                     if (!really_quiet)
1090                         error (0, 0, "warning: no branch `%s' in `%s'",
1091                                  r->first, rcs->print_path);
1092                     nr->last = NULL;
1093                     nr->fields = 0;
1094               }
1095               else
1096               {
1097                     nr->last = xstrdup (nr->first);
1098                     nr->fields = numdots (nr->first) + 1;
1099               }
1100           }
1101           else
1102           {
1103               if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
1104                     nr->first = xstrdup (r->first);
1105               else
1106               {
1107                     if (baserev && strcmp (r->first, TAG_BASE) == 0)
1108                         nr->first = xstrdup (baserev);
1109                     else if (RCS_nodeisbranch (rcs, r->first))
1110                         nr->first = RCS_whatbranch (rcs, r->first);
1111                     else
1112                         nr->first = RCS_gettag (rcs, r->first, 1, NULL);
1113                     if (nr->first == NULL && !really_quiet)
1114                     {
1115                         error (0, 0, "warning: no revision `%s' in `%s'",
1116                                  r->first, rcs->print_path);
1117                     }
1118               }
1119 
1120               if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1121                                                   strcmp (r->last, r->first) == 0))
1122                     nr->last = xstrdup (nr->first);
1123               else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
1124                     nr->last = xstrdup (r->last);
1125               else
1126               {
1127                     if (baserev && strcmp (r->last, TAG_BASE) == 0)
1128                         nr->last = xstrdup (baserev);
1129                     else if (RCS_nodeisbranch (rcs, r->last))
1130                         nr->last = RCS_whatbranch (rcs, r->last);
1131                     else
1132                         nr->last = RCS_gettag (rcs, r->last, 1, NULL);
1133                     if (nr->last == NULL && !really_quiet)
1134                     {
1135                         error (0, 0, "warning: no revision `%s' in `%s'",
1136                                  r->last, rcs->print_path);
1137                     }
1138               }
1139 
1140               /* Process the revision numbers the same way that rlog
1141                does.  This code is a bit cryptic for my tastes, but
1142                keeping the same implementation as rlog ensures a
1143                certain degree of compatibility.  */
1144               if (r->first == NULL && nr->last != NULL)
1145               {
1146                     nr->fields = numdots (nr->last) + 1;
1147                     if (nr->fields < 2)
1148                         nr->first = xstrdup (".0");
1149                     else
1150                     {
1151                         char *cp;
1152 
1153                         nr->first = xstrdup (nr->last);
1154                         cp = strrchr (nr->first, '.');
1155                         assert (cp);
1156                         strcpy (cp + 1, "0");
1157                     }
1158               }
1159               else if (r->last == NULL && nr->first != NULL)
1160               {
1161                     nr->fields = numdots (nr->first) + 1;
1162                     nr->last = xstrdup (nr->first);
1163                     if (nr->fields < 2)
1164                         nr->last[0] = '\0';
1165                     else
1166                     {
1167                         char *cp;
1168 
1169                         cp = strrchr (nr->last, '.');
1170                         assert (cp);
1171                         *cp = '\0';
1172                     }
1173               }
1174               else if (nr->first == NULL || nr->last == NULL)
1175                     nr->fields = 0;
1176               else if (strcmp (nr->first, nr->last) == 0)
1177                     nr->fields = numdots (nr->last) + 1;
1178               else
1179               {
1180                     int ord;
1181                     int dots1 = numdots (nr->first);
1182                     int dots2 = numdots (nr->last);
1183                     if (dots1 > dots2 || (dots1 == dots2 &&
1184                         version_compare (nr->first, nr->last, dots1 + 1) > 0))
1185                     {
1186                         char *tmp = nr->first;
1187                         nr->first = nr->last;
1188                         nr->last = tmp;
1189                         nr->fields = dots2 + 1;
1190                         dots2 = dots1;
1191                         dots1 = nr->fields - 1;
1192                     }
1193                     else
1194                         nr->fields = dots1 + 1;
1195                     dots1 += (nr->fields & 1);
1196                     ord = version_compare (nr->first, nr->last, dots1);
1197                     if (ord > 0 || (nr->fields > 2 && ord < 0))
1198                     {
1199                         error (0, 0,
1200                                  "invalid branch or revision pair %s:%s in `%s'",
1201                                  r->first, r->last, rcs->print_path);
1202                         free (nr->first);
1203                         nr->first = NULL;
1204                         free (nr->last);
1205                         nr->last = NULL;
1206                         nr->fields = 0;
1207                     }
1208                     else
1209                     {
1210                         if (nr->fields <= dots2 && (nr->fields & 1))
1211                         {
1212                               char *p = Xasprintf ("%s.0", nr->first);
1213                               free (nr->first);
1214                               nr->first = p;
1215                               ++nr->fields;
1216                         }
1217                         while (nr->fields <= dots2)
1218                         {
1219                               char *p;
1220                               int i;
1221 
1222                               nr->next = NULL;
1223                               *pr = nr;
1224                               nr = xmalloc (sizeof *nr);
1225                               nr->inclusive = 1;
1226                               nr->first = xstrdup ((*pr)->last);
1227                               nr->last = xstrdup ((*pr)->last);
1228                               nr->fields = (*pr)->fields;
1229                               p = (*pr)->last;
1230                               for (i = 0; i < nr->fields; i++)
1231                                   p = strchr (p, '.') + 1;
1232                               p[-1] = '\0';
1233                               p = strchr (nr->first + (p - (*pr)->last), '.');
1234                               if (p != NULL)
1235                               {
1236                                   *++p = '0';
1237                                   *++p = '\0';
1238                                   nr->fields += 2;
1239                               }
1240                               else
1241                                   ++nr->fields;
1242                               pr = &(*pr)->next;
1243                         }
1244                     }
1245               }
1246           }
1247 
1248           nr->next = NULL;
1249           *pr = nr;
1250           pr = &nr->next;
1251     }
1252 
1253     /* If the default branch was requested, add a revlist entry for
1254        it.  This is how rlog handles this option.  */
1255     if (default_branch
1256           && (rcs->head != NULL || rcs->branch != NULL))
1257     {
1258           struct revlist *nr;
1259 
1260           nr = xmalloc (sizeof *nr);
1261           if (rcs->branch != NULL)
1262               nr->first = xstrdup (rcs->branch);
1263           else
1264           {
1265               char *cp;
1266 
1267               nr->first = xstrdup (rcs->head);
1268               assert (nr->first);
1269               cp = strrchr (nr->first, '.');
1270               assert (cp);
1271               *cp = '\0';
1272           }
1273           nr->last = xstrdup (nr->first);
1274           nr->fields = numdots (nr->first) + 1;
1275           nr->inclusive = 1;
1276 
1277           nr->next = NULL;
1278           *pr = nr;
1279     }
1280 
1281     return ret;
1282 }
1283 
1284 
1285 
1286 /*
1287  * Free a revlist created by log_expand_revlist.
1288  */
1289 static void
log_free_revlist(struct revlist * revlist)1290 log_free_revlist (struct revlist *revlist)
1291 {
1292     struct revlist *r;
1293 
1294     r = revlist;
1295     while (r != NULL)
1296     {
1297           struct revlist *next;
1298 
1299           if (r->first != NULL)
1300               free (r->first);
1301           if (r->last != NULL)
1302               free (r->last);
1303           next = r->next;
1304           free (r);
1305           r = next;
1306     }
1307 }
1308 
1309 
1310 
1311 /*
1312  * Return nonzero if a revision should be printed, based on the
1313  * options provided.
1314  */
1315 static int
log_version_requested(struct log_data * log_data,struct revlist * revlist,RCSNode * rcs,RCSVers * vnode)1316 log_version_requested (struct log_data *log_data, struct revlist *revlist,
1317                        RCSNode *rcs, RCSVers *vnode)
1318 {
1319     /* Handle the list of states from the -s option.  */
1320     if (log_data->statelist != NULL
1321           && findnode (log_data->statelist, vnode->state) == NULL)
1322     {
1323           return 0;
1324     }
1325 
1326     /* Handle the list of authors from the -w option.  */
1327     if (log_data->authorlist != NULL)
1328     {
1329           if (vnode->author != NULL
1330               && findnode (log_data->authorlist, vnode->author) == NULL)
1331           {
1332               return 0;
1333           }
1334     }
1335 
1336     /* rlog considers all the -d options together when it decides
1337        whether to print a revision, so we must be compatible.  */
1338     if (log_data->datelist != NULL || log_data->singledatelist != NULL)
1339     {
1340           struct datelist *d;
1341 
1342           for (d = log_data->datelist; d != NULL; d = d->next)
1343           {
1344               int cmp;
1345 
1346               cmp = RCS_datecmp (vnode->date, d->start);
1347               if (cmp > 0 || (cmp == 0 && d->inclusive))
1348               {
1349                     cmp = RCS_datecmp (vnode->date, d->end);
1350                     if (cmp < 0 || (cmp == 0 && d->inclusive))
1351                         break;
1352               }
1353           }
1354 
1355           if (d == NULL)
1356           {
1357               /* Look through the list of specific dates.  We want to
1358                  select the revision with the exact date found in the
1359                  start field.  The commit code ensures that it is
1360                  impossible to check in multiple revisions of a single
1361                  file in a single second, so checking the date this way
1362                  should never select more than one revision.  */
1363               for (d = log_data->singledatelist; d != NULL; d = d->next)
1364               {
1365                     if (d->start != NULL
1366                         && RCS_datecmp (vnode->date, d->start) == 0)
1367                     {
1368                         break;
1369                     }
1370               }
1371 
1372               if (d == NULL)
1373                     return 0;
1374           }
1375     }
1376 
1377     /* If the -r or -b options were used, REVLIST will be non NULL,
1378        and we print the union of the specified revisions.  */
1379     if (revlist != NULL)
1380     {
1381           char *v;
1382           int vfields;
1383           struct revlist *r;
1384 
1385           /* This code is taken from rlog.  */
1386           v = vnode->version;
1387           vfields = numdots (v) + 1;
1388           for (r = revlist; r != NULL; r = r->next)
1389           {
1390             if (vfields == r->fields + (r->fields & 1) &&
1391                 (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1392                                 version_compare (v, r->first, r->fields) > 0)
1393                 && version_compare (v, r->last, r->fields) <= 0)
1394               {
1395                     return 1;
1396               }
1397           }
1398 
1399           /* If we get here, then the -b and/or the -r option was used,
1400            but did not match this revision, so we reject it.  */
1401 
1402           return 0;
1403     }
1404 
1405     /* By default, we print all revisions.  */
1406     return 1;
1407 }
1408 
1409 
1410 
1411 /*
1412  * Output a single symbol.  This is called via walklist.
1413  */
1414 /*ARGSUSED*/
1415 static int
log_symbol(Node * p,void * closure)1416 log_symbol (Node *p, void *closure)
1417 {
1418     cvs_output ("\n\t", 2);
1419     cvs_output (p->key, 0);
1420     cvs_output (": ", 2);
1421     cvs_output (p->data, 0);
1422     return 0;
1423 }
1424 
1425 
1426 
1427 /*
1428  * Count the number of entries on a list.  This is called via walklist.
1429  */
1430 /*ARGSUSED*/
1431 static int
log_count(Node * p,void * closure)1432 log_count (Node *p, void *closure)
1433 {
1434     return 1;
1435 }
1436 
1437 
1438 
1439 /*
1440  * Sort out a single date specification by narrowing down the date
1441  * until we find the specific selected revision.
1442  */
1443 static int
log_fix_singledate(Node * p,void * closure)1444 log_fix_singledate (Node *p, void *closure)
1445 {
1446     struct log_data_and_rcs *data = closure;
1447     Node *pv;
1448     RCSVers *vnode;
1449     struct datelist *holdsingle, *holddate;
1450     int requested;
1451 
1452     pv = findnode (data->rcs->versions, p->key);
1453     if (pv == NULL)
1454           error (1, 0, "missing version `%s' in RCS file `%s'",
1455                  p->key, data->rcs->print_path);
1456     vnode = pv->data;
1457 
1458     /* We are only interested if this revision passes any other tests.
1459        Temporarily clear log_data->singledatelist to avoid confusing
1460        log_version_requested.  We also clear log_data->datelist,
1461        because rlog considers all the -d options together.  We don't
1462        want to reject a revision because it does not match a date pair
1463        if we are going to select it on the basis of the singledate.  */
1464     holdsingle = data->log_data->singledatelist;
1465     data->log_data->singledatelist = NULL;
1466     holddate = data->log_data->datelist;
1467     data->log_data->datelist = NULL;
1468     requested = log_version_requested (data->log_data, data->revlist,
1469                                                data->rcs, vnode);
1470     data->log_data->singledatelist = holdsingle;
1471     data->log_data->datelist = holddate;
1472 
1473     if (requested)
1474     {
1475           struct datelist *d;
1476 
1477           /* For each single date, if this revision is before the
1478              specified date, but is closer than the previously selected
1479              revision, select it instead.  */
1480           for (d = data->log_data->singledatelist; d != NULL; d = d->next)
1481           {
1482               if (RCS_datecmp (vnode->date, d->end) <= 0
1483                     && (d->start == NULL
1484                         || RCS_datecmp (vnode->date, d->start) > 0))
1485               {
1486                     if (d->start != NULL)
1487                         free (d->start);
1488                     d->start = xstrdup (vnode->date);
1489               }
1490           }
1491     }
1492 
1493     return 0;
1494 }
1495 
1496 
1497 
1498 /*
1499  * Count the number of revisions we are going to print.
1500  */
1501 static int
log_count_print(Node * p,void * closure)1502 log_count_print (Node *p, void *closure)
1503 {
1504     struct log_data_and_rcs *data = closure;
1505     Node *pv;
1506 
1507     pv = findnode (data->rcs->versions, p->key);
1508     if (pv == NULL)
1509           error (1, 0, "missing version `%s' in RCS file `%s'",
1510                  p->key, data->rcs->print_path);
1511     if (log_version_requested (data->log_data, data->revlist, data->rcs,
1512                                      pv->data))
1513           return 1;
1514     else
1515           return 0;
1516 }
1517 
1518 
1519 
1520 /*
1521  * Print the list of changes, not including the trunk, in reverse
1522  * order for each branch.
1523  */
1524 static void
log_tree(struct log_data * log_data,struct revlist * revlist,RCSNode * rcs,const char * ver)1525 log_tree (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1526           const char *ver)
1527 {
1528     Node *p;
1529     RCSVers *vnode;
1530 
1531     p = findnode (rcs->versions, ver);
1532     if (p == NULL)
1533           error (1, 0, "missing version `%s' in RCS file `%s'",
1534                  ver, rcs->print_path);
1535     vnode = p->data;
1536     if (vnode->next != NULL)
1537           log_tree (log_data, revlist, rcs, vnode->next);
1538     if (vnode->branches != NULL)
1539     {
1540           Node *head, *branch;
1541 
1542           /* We need to do the branches in reverse order.  This breaks
1543            the List abstraction, but so does most of the branch
1544            manipulation in rcs.c.  */
1545           head = vnode->branches->list;
1546           for (branch = head->prev; branch != head; branch = branch->prev)
1547           {
1548               log_abranch (log_data, revlist, rcs, branch->key);
1549               log_tree (log_data, revlist, rcs, branch->key);
1550           }
1551     }
1552 }
1553 
1554 
1555 
1556 /*
1557  * Log the changes for a branch, in reverse order.
1558  */
1559 static void
log_abranch(struct log_data * log_data,struct revlist * revlist,RCSNode * rcs,const char * ver)1560 log_abranch (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1561              const char *ver)
1562 {
1563     Node *p;
1564     RCSVers *vnode;
1565 
1566     p = findnode (rcs->versions, ver);
1567     if (p == NULL)
1568           error (1, 0, "missing version `%s' in RCS file `%s'",
1569                  ver, rcs->print_path);
1570     vnode = p->data;
1571     if (vnode->next != NULL)
1572           log_abranch (log_data, revlist, rcs, vnode->next);
1573     log_version (log_data, revlist, rcs, vnode, 0);
1574 }
1575 
1576 
1577 
1578 /*
1579  * Print the log output for a single version.
1580  */
1581 static void
log_version(struct log_data * log_data,struct revlist * revlist,RCSNode * rcs,RCSVers * ver,int trunk)1582 log_version (struct log_data *log_data, struct revlist *revlist, RCSNode *rcs,
1583              RCSVers *ver, int trunk)
1584 {
1585     Node *p;
1586     int year, mon, mday, hour, min, sec;
1587     char buf[100];
1588     Node *padd, *pdel;
1589 
1590     if (! log_version_requested (log_data, revlist, rcs, ver))
1591           return;
1592 
1593     cvs_output ("----------------------------\nrevision ", 0);
1594     cvs_output (ver->version, 0);
1595 
1596     p = findnode (RCS_getlocks (rcs), ver->version);
1597     if (p != NULL)
1598     {
1599           cvs_output ("\tlocked by: ", 0);
1600           cvs_output (p->data, 0);
1601           cvs_output (";", 1);
1602     }
1603     cvs_output ("\n", 1);
1604 
1605     cvs_output_tagged ("text", "date: ");
1606     (void)sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
1607                       &sec);
1608     if (year < 1900)
1609           year += 1900;
1610     sprintf (buf, "%04d-%02d-%02d %02d:%02d:%02d +0000", year, mon, mday,
1611                hour, min, sec);
1612     cvs_output_tagged ("date", buf);
1613 
1614     cvs_output_tagged ("text", ";  author: ");
1615     cvs_output_tagged ("text", ver->author);
1616 
1617     cvs_output_tagged ("text", ";  state: ");
1618     cvs_output_tagged ("text", ver->state);
1619     cvs_output_tagged ("text", ";");
1620 
1621     if (! trunk)
1622     {
1623           padd = findnode (ver->other, ";add");
1624           pdel = findnode (ver->other, ";delete");
1625     }
1626     else if (ver->next == NULL)
1627     {
1628           padd = NULL;
1629           pdel = NULL;
1630     }
1631     else
1632     {
1633           Node *nextp;
1634           RCSVers *nextver;
1635 
1636           nextp = findnode (rcs->versions, ver->next);
1637           if (nextp == NULL)
1638               error (1, 0, "missing version `%s' in `%s'", ver->next,
1639                        rcs->print_path);
1640           nextver = nextp->data;
1641           pdel = findnode (nextver->other, ";add");
1642           padd = findnode (nextver->other, ";delete");
1643     }
1644 
1645     if (padd != NULL)
1646     {
1647           assert (pdel);
1648           cvs_output_tagged ("text", "  lines: +");
1649           cvs_output_tagged ("text", padd->data);
1650           cvs_output_tagged ("text", " -");
1651           cvs_output_tagged ("text", pdel->data);
1652         cvs_output_tagged ("text", ";");
1653     }
1654 
1655     p = findnode(ver->other_delta,"commitid");
1656     if(p && p->data)
1657     {
1658         cvs_output_tagged ("text", "  commitid: ");
1659           cvs_output_tagged ("text", p->data);
1660           cvs_output_tagged ("text", ";");
1661     }
1662 
1663     cvs_output_tagged ("newline", NULL);
1664 
1665     if (ver->branches != NULL)
1666     {
1667           cvs_output ("branches:", 0);
1668           walklist (ver->branches, log_branch, NULL);
1669           cvs_output ("\n", 1);
1670     }
1671 
1672     p = findnode (ver->other, "log");
1673     /* The p->date == NULL case is the normal one for an empty log
1674        message (rcs-14 in sanity.sh).  I don't think the case where
1675        p->data is "" can happen (getrcskey in rcs.c checks for an
1676        empty string and set the value to NULL in that case).  My guess
1677        would be the p == NULL case would mean an RCS file which was
1678        missing the "log" keyword (which is invalid according to
1679        rcsfile.5).  */
1680     if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
1681           cvs_output ("*** empty log message ***\n", 0);
1682     else
1683     {
1684           /* FIXME: Technically, the log message could contain a null
1685            byte.  */
1686           cvs_output (p->data, 0);
1687           if (((char *)p->data)[strlen (p->data) - 1] != '\n')
1688               cvs_output ("\n", 1);
1689     }
1690 }
1691 
1692 
1693 
1694 /*
1695  * Output a branch version.  This is called via walklist.
1696  */
1697 /*ARGSUSED*/
1698 static int
log_branch(Node * p,void * closure)1699 log_branch (Node *p, void *closure)
1700 {
1701     cvs_output ("  ", 2);
1702     if ((numdots (p->key) & 1) == 0)
1703           cvs_output (p->key, 0);
1704     else
1705     {
1706           char *f, *cp;
1707 
1708           f = xstrdup (p->key);
1709           cp = strrchr (f, '.');
1710           *cp = '\0';
1711           cvs_output (f, 0);
1712           free (f);
1713     }
1714     cvs_output (";", 1);
1715     return 0;
1716 }
1717 
1718 
1719 
1720 /*
1721  * Print a warm fuzzy message
1722  */
1723 /* ARGSUSED */
1724 static Dtype
log_dirproc(void * callerdat,const char * dir,const char * repository,const char * update_dir,List * entries)1725 log_dirproc (void *callerdat, const char *dir, const char *repository,
1726              const char *update_dir, List *entries)
1727 {
1728     if (!isdir (dir))
1729           return R_SKIP_ALL;
1730 
1731     if (!quiet)
1732           error (0, 0, "Logging %s", update_dir);
1733     return R_PROCESS;
1734 }
1735 
1736 
1737 
1738 /*
1739  * Compare versions.  This is taken from RCS compartial.
1740  */
1741 static int
version_compare(const char * v1,const char * v2,int len)1742 version_compare (const char *v1, const char *v2, int len)
1743 {
1744     while (1)
1745     {
1746           int d1, d2, r;
1747 
1748           if (*v1 == '\0')
1749               return 1;
1750           if (*v2 == '\0')
1751               return -1;
1752 
1753           while (*v1 == '0')
1754               ++v1;
1755           for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
1756               ;
1757 
1758           while (*v2 == '0')
1759               ++v2;
1760           for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
1761               ;
1762 
1763           if (d1 != d2)
1764               return d1 < d2 ? -1 : 1;
1765 
1766           r = memcmp (v1, v2, d1);
1767           if (r != 0)
1768               return r;
1769 
1770           --len;
1771           if (len == 0)
1772               return 0;
1773 
1774           v1 += d1;
1775           v2 += d1;
1776 
1777           if (*v1 == '.')
1778               ++v1;
1779           if (*v2 == '.')
1780               ++v2;
1781     }
1782 }
1783