home *** CD-ROM | disk | FTP | other *** search
/ Geek Gadgets 1 / ADE-1.bin / ade-dist / cvs-1.8.7-src.tgz / tar.out / fsf / cvs / src / log.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  34KB  |  1,451 lines

  1. /*
  2.  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  3.  * Copyright (c) 1989-1992, Brian Berliner
  4.  * 
  5.  * You may distribute under the terms of the GNU General Public License as
  6.  * specified in the README file that comes with the CVS 1.4 kit.
  7.  * 
  8.  * Print Log Information
  9.  * 
  10.  * This line exists solely to test some pcl-cvs/ChangeLog stuff.  You
  11.  * can delete it, if indeed it's still here when you read it.  -Karl
  12.  *
  13.  * Prints the RCS "log" (rlog) information for the specified files.  With no
  14.  * argument, prints the log information for all the files in the directory
  15.  * (recursive by default).
  16.  */
  17.  
  18. #include "cvs.h"
  19.  
  20. /* This structure holds information parsed from the -r option.  */
  21.  
  22. struct option_revlist
  23. {
  24.     /* The next -r option.  */
  25.     struct option_revlist *next;
  26.     /* The first revision to print.  This is NULL if the range is
  27.        :rev, or if no revision is given.  */
  28.     char *first;
  29.     /* The last revision to print.  This is NULL if the range is rev:,
  30.        or if no revision is given.  If there is no colon, first and
  31.        last are the same.  */
  32.     char *last;
  33.     /* Nonzero if there was a trailing `.', which means to print only
  34.        the head revision of a branch.  */
  35.     int branchhead;
  36. };
  37.  
  38. /* This structure holds information derived from option_revlist given
  39.    a particular RCS file.  */
  40.  
  41. struct revlist
  42. {
  43.     /* The next pair.  */
  44.     struct revlist *next;
  45.     /* The first numeric revision to print.  */
  46.     char *first;
  47.     /* The last numeric revision to print.  */
  48.     char *last;
  49.     /* The number of fields in these revisions (one more than
  50.        numdots).  */
  51.     int fields;
  52. };
  53.  
  54. /* This structure holds information parsed from the -d option.  */
  55.  
  56. struct datelist
  57. {
  58.     /* The next date.  */
  59.     struct datelist *next;
  60.     /* The starting date.  */
  61.     char *start;
  62.     /* The ending date.  */
  63.     char *end;
  64.     /* Nonzero if the range is inclusive rather than exclusive.  */
  65.     int inclusive;
  66. };
  67.  
  68. /* This structure is used to pass information through start_recursion.  */
  69. struct log_data
  70. {
  71.     /* Nonzero if the -R option was given, meaning that only the name
  72.        of the RCS file should be printed.  */
  73.     int nameonly;
  74.     /* Nonzero if the -h option was given, meaning that only header
  75.        information should be printed.  */
  76.     int header;
  77.     /* Nonzero if the -t option was given, meaning that only the
  78.        header and the descriptive text should be printed.  */
  79.     int long_header;
  80.     /* Nonzero if the -N option was seen, meaning that tag information
  81.        should not be printed.  */
  82.     int notags;
  83.     /* Nonzero if the -b option was seen, meaning that only revisions
  84.        on the default branch should be printed.  */
  85.     int default_branch;
  86.     /* If not NULL, the value given for the -r option, which lists
  87.        sets of revisions to be printed.  */
  88.     struct option_revlist *revlist;
  89.     /* If not NULL, the date pairs given for the -d option, which
  90.        select date ranges to print.  */
  91.     struct datelist *datelist;
  92.     /* If not NULL, the single dates given for the -d option, which
  93.        select specific revisions to print based on a date.  */
  94.     struct datelist *singledatelist;
  95.     /* If not NULL, the list of states given for the -s option, which
  96.        only prints revisions of given states.  */
  97.     List *statelist;
  98.     /* If not NULL, the list of login names given for the -w option,
  99.        which only prints revisions checked in by given users.  */
  100.     List *authorlist;
  101. };
  102.  
  103. /* This structure is used to pass information through walklist.  */
  104. struct log_data_and_rcs
  105. {
  106.     struct log_data *log_data;
  107.     struct revlist *revlist;
  108.     RCSNode *rcs;
  109. };
  110.  
  111. static Dtype log_dirproc PROTO ((void *callerdat, char *dir,
  112.                  char *repository, char *update_dir,
  113.                  List *entries));
  114. static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  115. static struct option_revlist *log_parse_revlist PROTO ((const char *));
  116. static void log_parse_date PROTO ((struct log_data *, const char *));
  117. static void log_parse_list PROTO ((List **, const char *));
  118. static struct revlist *log_expand_revlist PROTO ((RCSNode *,
  119.                           struct option_revlist *,
  120.                           int));
  121. static void log_free_revlist PROTO ((struct revlist *));
  122. static int log_version_requested PROTO ((struct log_data *, struct revlist *,
  123.                      RCSNode *, RCSVers *));
  124. static int log_symbol PROTO ((Node *, void *));
  125. static int log_count PROTO ((Node *, void *));
  126. static int log_fix_singledate PROTO ((Node *, void *));
  127. static int log_count_print PROTO ((Node *, void *));
  128. static void log_tree PROTO ((struct log_data *, struct revlist *,
  129.                  RCSNode *, const char *));
  130. static void log_abranch PROTO ((struct log_data *, struct revlist *,
  131.                 RCSNode *, const char *));
  132. static void log_version PROTO ((struct log_data *, struct revlist *,
  133.                 RCSNode *, RCSVers *, int));
  134. static int log_branch PROTO ((Node *, void *));
  135. static int version_compare PROTO ((const char *, const char *, int));
  136.  
  137. static const char *const log_usage[] =
  138. {
  139.     "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
  140.     "    [-w[logins]] [files...]\n",
  141.     "\t-l\tLocal directory only, no recursion.\n",
  142.     "\t-R\tOnly print name of RCS file.\n",
  143.     "\t-h\tOnly print header.\n",
  144.     "\t-t\tOnly print header and descriptive text.\n",
  145.     "\t-N\tDo not list tags.\n",
  146.     "\t-b\tOnly list revisions on the default branch.\n",
  147.     "\t-r[revisions]\tSpecify revision(s)s to list.\n",
  148.     "\t-d dates\tSpecify dates (D1<D2 for range, D for latest before).\n",
  149.     "\t-s states\tOnly list revisions with specified states.\n",
  150.     "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
  151.     NULL
  152. };
  153.  
  154. int
  155. cvslog (argc, argv)
  156.     int argc;
  157.     char **argv;
  158. {
  159.     int c;
  160.     int err = 0;
  161.     int local = 0;
  162.     struct log_data log_data;
  163.     struct option_revlist *rl, **prl;
  164.  
  165.     if (argc == -1)
  166.     usage (log_usage);
  167.  
  168.     memset (&log_data, 0, sizeof log_data);
  169.  
  170.     optind = 1;
  171.     while ((c = getopt (argc, argv, "bd:hlNRr::s:tw::")) != -1)
  172.     {
  173.     switch (c)
  174.     {
  175.         case 'b':
  176.         log_data.default_branch = 1;
  177.         break;
  178.         case 'd':
  179.         log_parse_date (&log_data, optarg);
  180.         break;
  181.         case 'h':
  182.         log_data.header = 1;
  183.         break;
  184.         case 'l':
  185.         local = 1;
  186.         break;
  187.         case 'N':
  188.         log_data.notags = 1;
  189.         break;
  190.         case 'R':
  191.         log_data.nameonly = 1;
  192.         break;
  193.         case 'r':
  194.         rl = log_parse_revlist (optarg);
  195.         for (prl = &log_data.revlist;
  196.              *prl != NULL;
  197.              prl = &(*prl)->next)
  198.             ;
  199.         *prl = rl;
  200.         break;
  201.         case 's':
  202.         log_parse_list (&log_data.statelist, optarg);
  203.         break;
  204.         case 't':
  205.         log_data.long_header = 1;
  206.         break;
  207.         case 'w':
  208.         if (optarg != NULL)
  209.             log_parse_list (&log_data.authorlist, optarg);
  210.         else
  211.             log_parse_list (&log_data.authorlist, getcaller ());
  212.         break;
  213.         case '?':
  214.         default:
  215.         usage (log_usage);
  216.         break;
  217.     }
  218.     }
  219.  
  220.     wrap_setup ();
  221.  
  222. #ifdef CLIENT_SUPPORT
  223.     if (client_active)
  224.     {
  225.     int i;
  226.  
  227.     /* We're the local client.  Fire up the remote server.  */
  228.     start_server ();
  229.     
  230.     ign_setup ();
  231.  
  232.     for (i = 1; i < argc && argv[i][0] == '-'; i++)
  233.       send_arg (argv[i]);
  234.  
  235.     send_file_names (argc - i, argv + i, SEND_EXPAND_WILD);
  236. /* FIXME:  We shouldn't have to send current files to get log entries, but it
  237.    doesn't work yet and I haven't debugged it.  So send the files --
  238.    it's slower but it works.  gnu@cygnus.com  Apr94  */
  239.     send_files (argc - i, argv + i, local, 0);
  240.  
  241.     send_to_server ("log\012", 0);
  242.         err = get_responses_and_close ();
  243.     return err;
  244.     }
  245. #endif
  246.  
  247.     err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
  248.                (DIRLEAVEPROC) NULL, (void *) &log_data,
  249.                argc - optind, argv + optind, local,
  250.                W_LOCAL | W_REPOS | W_ATTIC, 0, 1,
  251.                (char *) NULL, 1);
  252.     return (err);
  253. }
  254.  
  255. /*
  256.  * Parse a revision list specification.
  257.  */
  258.  
  259. static struct option_revlist *
  260. log_parse_revlist (argstring)
  261.     const char *argstring;
  262. {
  263.     char *copy;
  264.     struct option_revlist *ret, **pr;
  265.  
  266.     /* Unfortunately, rlog accepts -r without an argument to mean that
  267.        latest revision on the default branch, so we must support that
  268.        for compatibility.  */
  269.     if (argstring == NULL)
  270.     {
  271.     ret = (struct option_revlist *) xmalloc (sizeof *ret);
  272.     ret->first = NULL;
  273.     ret->last = NULL;
  274.     ret->next = NULL;
  275.     ret->branchhead = 0;
  276.     return ret;
  277.     }
  278.  
  279.     ret = NULL;
  280.     pr = &ret;
  281.  
  282.     /* Copy the argument into memory so that we can change it.  We
  283.        don't want to change the argument because, at least as of this
  284.        writing, we will use it if we send the arguments to the server.
  285.        We never bother to free up our copy.  */
  286.     copy = xstrdup (argstring);
  287.     while (copy != NULL)
  288.     {
  289.     char *comma;
  290.     char *cp;
  291.     char *first, *last;
  292.     struct option_revlist *r;
  293.  
  294.     comma = strchr (copy, ',');
  295.     if (comma != NULL)
  296.         *comma++ = '\0';
  297.  
  298.     first = copy;
  299.     cp = strchr (copy, ':');
  300.     if (cp == NULL)
  301.         last = copy;
  302.     else
  303.     {
  304.         *cp++ = '\0';
  305.         last = cp;
  306.     }
  307.  
  308.     if (*first == '\0')
  309.         first = NULL;
  310.     if (*last == '\0')
  311.         last = NULL;
  312.  
  313.     r = (struct option_revlist *) xmalloc (sizeof *r);
  314.     r->next = NULL;
  315.     r->first = first;
  316.     r->last = last;
  317.     if (first != last
  318.         || first[strlen (first) - 1] != '.')
  319.     {
  320.         r->branchhead = 0;
  321.     }
  322.     else
  323.     {
  324.         r->branchhead = 1;
  325.         first[strlen (first) - 1] = '\0';
  326.     }
  327.  
  328.     *pr = r;
  329.     pr = &r->next;
  330.  
  331.     copy = comma;
  332.     }
  333.  
  334.     return ret;
  335. }
  336.  
  337. /*
  338.  * Parse a date specification.
  339.  */
  340. static void
  341. log_parse_date (log_data, argstring)
  342.     struct log_data *log_data;
  343.     const char *argstring;
  344. {
  345.     char *orig_copy, *copy;
  346.  
  347.     /* Copy the argument into memory so that we can change it.  We
  348.        don't want to change the argument because, at least as of this
  349.        writing, we will use it if we send the arguments to the server.  */
  350.     copy = xstrdup (argstring);
  351.     orig_copy = copy;
  352.     while (copy != NULL)
  353.     {
  354.     struct datelist *nd, **pd;
  355.     char *cpend, *cp, *ds, *de;
  356.  
  357.     nd = (struct datelist *) xmalloc (sizeof *nd);
  358.  
  359.     cpend = strchr (copy, ';');
  360.     if (cpend != NULL)
  361.         *cpend++ = '\0';
  362.  
  363.     pd = &log_data->datelist;
  364.     nd->inclusive = 0;
  365.  
  366.     if ((cp = strchr (copy, '>')) != NULL)
  367.     {
  368.         *cp++ = '\0';
  369.         if (*cp == '=')
  370.         {
  371.         ++cp;
  372.         nd->inclusive = 1;
  373.         }
  374.         ds = cp;
  375.         de = copy;
  376.     }
  377.     else if ((cp = strchr (copy, '<')) != NULL)
  378.     {
  379.         *cp++ = '\0';
  380.         if (*cp == '=')
  381.         {
  382.         ++cp;
  383.         nd->inclusive = 1;
  384.         }
  385.         ds = copy;
  386.         de = cp;
  387.     }
  388.     else
  389.     {
  390.         ds = NULL;
  391.         de = copy;
  392.         pd = &log_data->singledatelist;
  393.     }
  394.  
  395.     if (ds == NULL)
  396.         nd->start = NULL;
  397.     else if (*ds != '\0')
  398.         nd->start = Make_Date (ds);
  399.     else
  400.     {
  401.       /* 1970 was the beginning of time, as far as get_date and
  402.          Make_Date are concerned.  */
  403.         nd->start = Make_Date ("1/1/1970 UTC");
  404.     }
  405.  
  406.     if (*de != '\0')
  407.         nd->end = Make_Date (de);
  408.     else
  409.     {
  410.       /* We want to set the end date to some time sufficiently far
  411.          in the future to pick up all revisions that have been
  412.          created since the specified date and the time `cvs log'
  413.          completes.  */
  414.         nd->end = Make_Date ("next week");
  415.     }
  416.  
  417.     nd->next = *pd;
  418.     *pd = nd;
  419.  
  420.     copy = cpend;
  421.     }
  422.  
  423.     free (orig_copy);
  424. }
  425.  
  426. /*
  427.  * Parse a comma separated list of items, and add each one to *PLIST.
  428.  */
  429. static void
  430. log_parse_list (plist, argstring)
  431.     List **plist;
  432.     const char *argstring;
  433. {
  434.     while (1)
  435.     {
  436.     Node *p;
  437.     char *cp;
  438.  
  439.     p = getnode ();
  440.  
  441.     cp = strchr (argstring, ',');
  442.     if (cp == NULL)
  443.         p->key = xstrdup (argstring);
  444.     else
  445.     {
  446.         size_t len;
  447.  
  448.         len = cp - argstring;
  449.         p->key = xmalloc (len + 1);
  450.         strncpy (p->key, argstring, len);
  451.         p->key[len + 1] = '\0';
  452.     }
  453.  
  454.     if (*plist == NULL)
  455.         *plist = getlist ();
  456.     if (addnode (*plist, p) != 0)
  457.         freenode (p);
  458.  
  459.     if (cp == NULL)
  460.         break;
  461.  
  462.     argstring = cp + 1;
  463.     }
  464. }
  465.  
  466. /*
  467.  * Do an rlog on a file
  468.  */
  469. static int
  470. log_fileproc (callerdat, finfo)
  471.     void *callerdat;
  472.     struct file_info *finfo;
  473. {
  474.     struct log_data *log_data = (struct log_data *) callerdat;
  475.     Node *p;
  476.     RCSNode *rcsfile;
  477.     char buf[50];
  478.     struct revlist *revlist;
  479.     struct log_data_and_rcs log_data_and_rcs;
  480.  
  481.     if ((rcsfile = finfo->rcs) == NULL)
  482.     {
  483.     /* no rcs file.  What *do* we know about this file? */
  484.     p = findnode (finfo->entries, finfo->file);
  485.     if (p != NULL)
  486.     {
  487.         Entnode *e;
  488.         
  489.         e = (Entnode *) p->data;
  490.         if (e->version[0] == '0' || e->version[1] == '\0')
  491.         {
  492.         if (!really_quiet)
  493.             error (0, 0, "%s has been added, but not committed",
  494.                finfo->file);
  495.         return(0);
  496.         }
  497.     }
  498.     
  499.     if (!really_quiet)
  500.         error (0, 0, "nothing known about %s", finfo->file);
  501.     
  502.     return (1);
  503.     }
  504.  
  505.     if (log_data->nameonly)
  506.     {
  507.     cvs_output (rcsfile->path, 0);
  508.     cvs_output ("\n", 1);
  509.     return 0;
  510.     }
  511.  
  512.     /* We will need all the information in the RCS file.  */
  513.     RCS_fully_parse (rcsfile);
  514.  
  515.     /* Turn any symbolic revisions in the revision list into numeric
  516.        revisions.  */
  517.     revlist = log_expand_revlist (rcsfile, log_data->revlist,
  518.                   log_data->default_branch);
  519.  
  520.     /* The output here is intended to be exactly compatible with the
  521.        output of rlog.  I'm not sure whether this code should be here
  522.        or in rcs.c; I put it here because it is specific to the log
  523.        function, even though it uses information gathered by the
  524.        functions in rcs.c.  */
  525.  
  526.     cvs_output ("\n", 1);
  527.  
  528.     cvs_output ("RCS file: ", 0);
  529.     cvs_output (rcsfile->path, 0);
  530.  
  531.     cvs_output ("\nWorking file: ", 0);
  532.     if (finfo->update_dir[0] == '\0')
  533.     cvs_output (finfo->file, 0);
  534.     else
  535.     {
  536.     cvs_output (finfo->update_dir, 0);
  537.     cvs_output ("/", 0);
  538.     cvs_output (finfo->file, 0);
  539.  
  540.     }
  541.  
  542.     cvs_output ("\nhead:", 0);
  543.     if (rcsfile->head != NULL)
  544.     {
  545.     cvs_output (" ", 1);
  546.     cvs_output (rcsfile->head, 0);
  547.     }
  548.  
  549.     cvs_output ("\nbranch:", 0);
  550.     if (rcsfile->branch != NULL)
  551.     {
  552.     cvs_output (" ", 1);
  553.     cvs_output (rcsfile->branch, 0);
  554.     }
  555.  
  556.     cvs_output ("\nlocks:", 0);
  557.     if (rcsfile->other != NULL)
  558.     {
  559.     p = findnode (rcsfile->other, "strict");
  560.     if (p != NULL)
  561.         cvs_output (" strict", 0);
  562.     p = findnode (rcsfile->other, "locks");
  563.     if (p != NULL && p->data != NULL)
  564.     {
  565.         char *f, *cp;
  566.  
  567.         f = xstrdup (p->data);
  568.         cp = f;
  569.         while (*cp != '\0')
  570.         {
  571.         char *cp2, *locker, *version;
  572.         RCSVers *vnode;
  573.  
  574.         locker = cp;
  575.  
  576.         cp2 = strchr (cp, ':');
  577.         if (cp2 == NULL)
  578.         {
  579.             error (0, 0, "warning: bad locks field in RCS file `%s'",
  580.                finfo->fullname);
  581.             break;
  582.         }
  583.  
  584.         *cp2 = '\0';
  585.  
  586.         cvs_output ("\n\t", 2);
  587.         cvs_output (cp, cp2 - cp);
  588.         cvs_output (": ", 2);
  589.  
  590.         cp = cp2 + 1;
  591.         while (isspace (*cp) && *cp != '\0')
  592.             ++cp;
  593.  
  594.         version = cp;
  595.  
  596.         cp2 = cp;
  597.         while (! isspace (*cp2) && *cp2 != '\0')
  598.             ++cp2;
  599.  
  600.         cvs_output (cp, cp2 - cp);
  601.  
  602.         if (*cp2 == '\0')
  603.             cp = cp2;
  604.         else
  605.         {
  606.             *cp2 = '\0';
  607.             cp = cp2 + 1;
  608.             while (isspace (*cp) && *cp != '\0')
  609.             ++cp;
  610.         }
  611.  
  612.         p = findnode (rcsfile->versions, version);
  613.         if (p == NULL)
  614.             error (0, 0,
  615.                "warning: lock for missing version `%s' in `%s'",
  616.                version, finfo->fullname);
  617.         else
  618.         {
  619.             vnode = (RCSVers *) p->data;
  620.             p = getnode ();
  621.             p->type = RCSFIELD;
  622.             p->key = xstrdup (";locker");
  623.             p->data = xstrdup (locker);
  624.             if (addnode (vnode->other, p) != 0)
  625.             {
  626.             error (0, 0,
  627.                    "warning: duplicate lock for `%s' in `%s'",
  628.                    version, finfo->fullname);
  629.             freenode (p);
  630.             }
  631.         }
  632.         }
  633.  
  634.         free (f);
  635.     }
  636.     }
  637.  
  638.     cvs_output ("\naccess list:", 0);
  639.     if (rcsfile->other != NULL)
  640.     {
  641.     p = findnode (rcsfile->other, "access");
  642.     if (p != NULL && p->data != NULL)
  643.     {
  644.         const char *cp;
  645.  
  646.         cp = p->data;
  647.         while (*cp != '\0')
  648.         {
  649.         const char *cp2;
  650.  
  651.         cvs_output ("\n\t", 2);
  652.         cp2 = cp;
  653.         while (! isspace (*cp2) && *cp2 != '\0')
  654.             ++cp2;
  655.         cvs_output (cp, cp2 - cp);
  656.         cp = cp2;
  657.         while (isspace (*cp) && *cp != '\0')
  658.             ++cp;
  659.         }
  660.     }
  661.     }
  662.  
  663.     if (! log_data->notags)
  664.     {
  665.     List *syms;
  666.  
  667.     cvs_output ("\nsymbolic names:", 0);
  668.     syms = RCS_symbols (rcsfile);
  669.     walklist (syms, log_symbol, NULL);
  670.     }
  671.  
  672.     cvs_output ("\nkeyword substitution: ", 0);
  673.     if (rcsfile->expand == NULL)
  674.     cvs_output ("kv", 2);
  675.     else
  676.     cvs_output (rcsfile->expand, 0);
  677.  
  678.     cvs_output ("\ntotal revisions: ", 0);
  679.     sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
  680.     cvs_output (buf, 0);
  681.  
  682.     if (! log_data->header && ! log_data->long_header)
  683.     {
  684.     cvs_output (";\tselected revisions: ", 0);
  685.  
  686.     log_data_and_rcs.log_data = log_data;
  687.     log_data_and_rcs.revlist = revlist;
  688.     log_data_and_rcs.rcs = rcsfile;
  689.  
  690.     /* If any single dates were specified, we need to identify the
  691.        revisions they select.  Each one selects the single
  692.        revision, which is otherwise selected, of that date or
  693.        earlier.  The log_fix_singledate routine will fill in the
  694.        start date for each specific revision.  */
  695.     if (log_data->singledatelist != NULL)
  696.         walklist (rcsfile->versions, log_fix_singledate,
  697.               (void *) &log_data_and_rcs);
  698.  
  699.     sprintf (buf, "%d", walklist (rcsfile->versions, log_count_print,
  700.                       (void *) &log_data_and_rcs));
  701.     cvs_output (buf, 0);
  702.     }
  703.  
  704.     cvs_output ("\n", 1);
  705.  
  706.     if (! log_data->header || log_data->long_header)
  707.     {
  708.     cvs_output ("description:\n", 0);
  709.     if (rcsfile->other != NULL)
  710.     {
  711.         p = findnode (rcsfile->other, "desc");
  712.         if (p != NULL)
  713.         cvs_output (p->data, 0);
  714.     }
  715.     }
  716.  
  717.     if (! log_data->header && ! log_data->long_header && rcsfile->head != NULL)
  718.     {
  719.     p = findnode (rcsfile->versions, rcsfile->head);
  720.     if (p == NULL)
  721.         error (1, 0, "can not find head revision in `%s'",
  722.            finfo->fullname);
  723.     while (p != NULL)
  724.     {
  725.         RCSVers *vers;
  726.  
  727.         vers = (RCSVers *) p->data;
  728.         log_version (log_data, revlist, rcsfile, vers, 1);
  729.         if (vers->next == NULL)
  730.         p = NULL;
  731.         else
  732.         {
  733.         p = findnode (rcsfile->versions, vers->next);
  734.         if (p == NULL)
  735.             error (1, 0, "can not find next revision `%s' in `%s'",
  736.                vers->next, finfo->fullname);
  737.         }
  738.     }
  739.  
  740.     log_tree (log_data, revlist, rcsfile, rcsfile->head);
  741.     }
  742.  
  743.     cvs_output("\
  744. =============================================================================\n",
  745.            0);
  746.  
  747.     /* Free up the new revlist and restore the old one.  */
  748.     log_free_revlist (revlist);
  749.  
  750.     /* If singledatelist is not NULL, free up the start dates we added
  751.        to it.  */
  752.     if (log_data->singledatelist != NULL)
  753.     {
  754.     struct datelist *d;
  755.  
  756.     for (d = log_data->singledatelist; d != NULL; d = d->next)
  757.     {
  758.         if (d->start != NULL)
  759.         free (d->start);
  760.         d->start = NULL;
  761.     }
  762.     }
  763.  
  764.     return 0;
  765. }
  766.  
  767. /*
  768.  * Fix up a revision list in order to compare it against versions.
  769.  * Expand any symbolic revisions.
  770.  */
  771. static struct revlist *
  772. log_expand_revlist (rcs, revlist, default_branch)
  773.     RCSNode *rcs;
  774.     struct option_revlist *revlist;
  775.     int default_branch;
  776. {
  777.     struct option_revlist *r;
  778.     struct revlist *ret, **pr;
  779.  
  780.     ret = NULL;
  781.     pr = &ret;
  782.     for (r = revlist; r != NULL; r = r->next)
  783.     {
  784.     struct revlist *nr;
  785.  
  786.     nr = (struct revlist *) xmalloc (sizeof *nr);
  787.  
  788.     if (r->first == NULL && r->last == NULL)
  789.     {
  790.         /* If both first and last are NULL, it means that we want
  791.            just the head of the default branch, which is RCS_head.  */
  792.         nr->first = RCS_head (rcs);
  793.         nr->last = xstrdup (nr->first);
  794.         nr->fields = numdots (nr->first) + 1;
  795.     }
  796.     else if (r->branchhead)
  797.     {
  798.         char *branch;
  799.  
  800.         /* Print just the head of the branch.  */
  801.         if (isdigit (r->first[0]))
  802.         nr->first = RCS_getbranch (rcs, r->first, 1);
  803.         else
  804.         {
  805.         branch = RCS_whatbranch (rcs, r->first);
  806.         if (branch == NULL)
  807.         {
  808.             error (0, 0, "warning: `%s' is not a branch in `%s'",
  809.                r->first, rcs->path);
  810.             free (nr);
  811.             continue;
  812.         }
  813.         nr->first = RCS_getbranch (rcs, branch, 1);
  814.         free (branch);
  815.         }
  816.         if (nr->first == NULL)
  817.         {
  818.         error (0, 0, "warning: no revision `%s' in `%s'",
  819.                r->first, rcs->path);
  820.         free (nr);
  821.         continue;
  822.         }
  823.         nr->last = xstrdup (nr->first);
  824.         nr->fields = numdots (nr->first) + 1;
  825.     }
  826.     else
  827.     {
  828.         if (r->first == NULL || isdigit (r->first[0]))
  829.         nr->first = xstrdup (r->first);
  830.         else
  831.         {
  832.         if (RCS_nodeisbranch (rcs, r->first))
  833.             nr->first = RCS_whatbranch (rcs, r->first);
  834.         else
  835.             nr->first = RCS_gettag (rcs, r->first, 1, 0);
  836.         if (nr->first == NULL)
  837.         {
  838.             error (0, 0, "warning: no revision `%s' in `%s'",
  839.                r->first, rcs->path);
  840.             free (nr);
  841.             continue;
  842.         }
  843.         }
  844.  
  845.         if (r->last == r->first)
  846.         nr->last = xstrdup (nr->first);
  847.         else if (r->last == NULL || isdigit (r->last[0]))
  848.         nr->last = xstrdup (r->last);
  849.         else
  850.         {
  851.         if (RCS_nodeisbranch (rcs, r->last))
  852.             nr->last = RCS_whatbranch (rcs, r->last);
  853.         else
  854.             nr->last = RCS_gettag (rcs, r->last, 1, 0);
  855.         if (nr->last == NULL)
  856.         {
  857.             error (0, 0, "warning: no revision `%s' in `%s'",
  858.                r->last, rcs->path);
  859.             if (nr->first != NULL)
  860.             free (nr->first);
  861.             free (nr);
  862.             continue;
  863.         }
  864.         }
  865.  
  866.         /* Process the revision numbers the same way that rlog
  867.                does.  This code is a bit cryptic for my tastes, but
  868.                keeping the same implementation as rlog ensures a
  869.                certain degree of compatibility.  */
  870.         if (r->first == NULL)
  871.         {
  872.         nr->fields = numdots (nr->last) + 1;
  873.         if (nr->fields < 2)
  874.             nr->first = xstrdup (".0");
  875.         else
  876.         {
  877.             char *cp;
  878.  
  879.             nr->first = xstrdup (nr->last);
  880.             cp = strrchr (nr->first, '.');
  881.             strcpy (cp, ".0");
  882.         }
  883.         }
  884.         else if (r->last == NULL)
  885.         {
  886.         nr->fields = numdots (nr->first) + 1;
  887.         nr->last = xstrdup (nr->first);
  888.         if (nr->fields < 2)
  889.             nr->last[0] = '\0';
  890.         else
  891.         {
  892.             char *cp;
  893.  
  894.             cp = strrchr (nr->last, '.');
  895.             *cp = '\0';
  896.         }
  897.         }
  898.         else
  899.         {
  900.         nr->fields = numdots (nr->first) + 1;
  901.         if (nr->fields != numdots (nr->last) + 1
  902.             || (nr->fields > 2
  903.             && version_compare (nr->first, nr->last,
  904.                         nr->fields - 1) != 0))
  905.         {
  906.             error (0, 0,
  907.                "invalid branch or revision pair %s:%s in `%s'",
  908.                r->first, r->last, rcs->path);
  909.             free (nr->first);
  910.             free (nr->last);
  911.             free (nr);
  912.             continue;
  913.         }
  914.         if (version_compare (nr->first, nr->last, nr->fields) > 0)
  915.         {
  916.             char *tmp;
  917.  
  918.             tmp = nr->first;
  919.             nr->first = nr->last;
  920.             nr->last = tmp;
  921.         }
  922.         }
  923.     }
  924.  
  925.     nr->next = NULL;
  926.     *pr = nr;
  927.     pr = &nr->next;
  928.     }
  929.  
  930.     /* If the default branch was requested, add a revlist entry for
  931.        it.  This is how rlog handles this option.  */
  932.     if (default_branch
  933.     && (rcs->head != NULL || rcs->branch != NULL))
  934.     {
  935.     struct revlist *nr;
  936.  
  937.     nr = (struct revlist *) xmalloc (sizeof *nr);
  938.     if (rcs->branch != NULL)
  939.         nr->first = xstrdup (rcs->branch);
  940.     else
  941.     {
  942.         char *cp;
  943.  
  944.         nr->first = xstrdup (rcs->head);
  945.         cp = strrchr (nr->first, '.');
  946.         *cp = '\0';
  947.     }
  948.     nr->last = xstrdup (nr->first);
  949.     nr->fields = numdots (nr->first) + 1;
  950.  
  951.     nr->next = NULL;
  952.     *pr = nr;
  953.     }
  954.  
  955.     return ret;
  956. }
  957.  
  958. /*
  959.  * Free a revlist created by log_expand_revlist.
  960.  */
  961. static void
  962. log_free_revlist (revlist)
  963.     struct revlist *revlist;
  964. {
  965.     struct revlist *r;
  966.  
  967.     r = revlist;
  968.     while (r != NULL)
  969.     {
  970.     struct revlist *next;
  971.  
  972.     if (r->first != NULL)
  973.         free (r->first);
  974.     if (r->last != NULL)
  975.         free (r->last);
  976.     next = r->next;
  977.     free (r);
  978.     r = next;
  979.     }
  980. }
  981.  
  982. /*
  983.  * Return nonzero if a revision should be printed, based on the
  984.  * options provided.
  985.  */
  986. static int
  987. log_version_requested (log_data, revlist, rcs, vnode)
  988.     struct log_data *log_data;
  989.     struct revlist *revlist;
  990.     RCSNode *rcs;
  991.     RCSVers *vnode;
  992. {
  993.     /* Handle the list of states from the -s option.  */
  994.     if (log_data->statelist != NULL)
  995.     {
  996.     Node *p;
  997.  
  998.     p = findnode (vnode->other, "state");
  999.     if (p != NULL
  1000.         && findnode (log_data->statelist, p->data) == NULL)
  1001.     {
  1002.         return 0;
  1003.     }
  1004.     }
  1005.  
  1006.     /* Handle the list of authors from the -w option.  */
  1007.     if (log_data->authorlist != NULL)
  1008.     {
  1009.     if (vnode->author != NULL
  1010.         && findnode (log_data->authorlist, vnode->author) == NULL)
  1011.     {
  1012.         return 0;
  1013.     }
  1014.     }
  1015.  
  1016.     /* rlog considers all the -d options together when it decides
  1017.        whether to print a revision, so we must be compatible.  */
  1018.     if (log_data->datelist != NULL || log_data->singledatelist != NULL)
  1019.     {
  1020.     struct datelist *d;
  1021.  
  1022.     for (d = log_data->datelist; d != NULL; d = d->next)
  1023.     {
  1024.         int cmp;
  1025.  
  1026.         cmp = RCS_datecmp (vnode->date, d->start);
  1027.         if (cmp > 0 || (cmp == 0 && d->inclusive))
  1028.         {
  1029.         cmp = RCS_datecmp (vnode->date, d->end);
  1030.         if (cmp < 0 || (cmp == 0 && d->inclusive))
  1031.             break;
  1032.         }
  1033.     }
  1034.  
  1035.     if (d == NULL)
  1036.     {
  1037.         /* Look through the list of specific dates.  We want to
  1038.            select the revision with the exact date found in the
  1039.            start field.  The commit code ensures that it is
  1040.            impossible to check in multiple revisions of a single
  1041.            file in a single second, so checking the date this way
  1042.            should never select more than one revision.  */
  1043.         for (d = log_data->singledatelist; d != NULL; d = d->next)
  1044.         {
  1045.         if (d->start != NULL
  1046.             && RCS_datecmp (vnode->date, d->start) == 0)
  1047.         {
  1048.             break;
  1049.         }
  1050.         }
  1051.  
  1052.         if (d == NULL)
  1053.         return 0;
  1054.     }
  1055.     }
  1056.  
  1057.     /* If the -r or -b options were used, REVLIST will be non NULL,
  1058.        and we print the union of the specified revisions.  */
  1059.     if (revlist != NULL)
  1060.     {
  1061.     char *v;
  1062.     int vfields;
  1063.     struct revlist *r;
  1064.  
  1065.     /* This code is taken from rlog.  */
  1066.     v = vnode->version;
  1067.     vfields = numdots (v) + 1;
  1068.     for (r = revlist; r != NULL; r = r->next)
  1069.     {
  1070.         if (vfields == r->fields + (r->fields & 1)
  1071.         && version_compare (v, r->first, r->fields) >= 0
  1072.         && version_compare (v, r->last, r->fields) <= 0)
  1073.         {
  1074.         return 1;
  1075.         }
  1076.     }
  1077.  
  1078.     /* If we get here, then the -b and/or the -r option was used,
  1079.            but did not match this revision, so we reject it.  */
  1080.  
  1081.     return 0;
  1082.     }
  1083.  
  1084.     /* By default, we print all revisions.  */
  1085.     return 1;
  1086. }
  1087.  
  1088. /*
  1089.  * Output a single symbol.  This is called via walklist.
  1090.  */
  1091. /*ARGSUSED*/
  1092. static int
  1093. log_symbol (p, closure)
  1094.     Node *p;
  1095.     void *closure;
  1096. {
  1097.     cvs_output ("\n\t", 2);
  1098.     cvs_output (p->key, 0);
  1099.     cvs_output (": ", 2);
  1100.     cvs_output (p->data, 0);
  1101.     return 0;
  1102. }
  1103.  
  1104. /*
  1105.  * Count the number of entries on a list.  This is called via walklist.
  1106.  */
  1107. /*ARGSUSED*/
  1108. static int
  1109. log_count (p, closure)
  1110.     Node *p;
  1111.     void *closure;
  1112. {
  1113.     return 1;
  1114. }
  1115.  
  1116. /*
  1117.  * Sort out a single date specification by narrowing down the date
  1118.  * until we find the specific selected revision.
  1119.  */
  1120. static int
  1121. log_fix_singledate (p, closure)
  1122.     Node *p;
  1123.     void *closure;
  1124. {
  1125.     struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
  1126.     Node *pv;
  1127.     RCSVers *vnode;
  1128.     struct datelist *holdsingle, *holddate;
  1129.     int requested;
  1130.  
  1131.     pv = findnode (data->rcs->versions, p->key);
  1132.     if (pv == NULL)
  1133.     error (1, 0, "missing version `%s' in RCS file `%s'",
  1134.            p->key, data->rcs->path);
  1135.     vnode = (RCSVers *) pv->data;
  1136.  
  1137.     /* We are only interested if this revision passes any other tests.
  1138.        Temporarily clear log_data->singledatelist to avoid confusing
  1139.        log_version_requested.  We also clear log_data->datelist,
  1140.        because rlog considers all the -d options together.  We don't
  1141.        want to reject a revision because it does not match a date pair
  1142.        if we are going to select it on the basis of the singledate.  */
  1143.     holdsingle = data->log_data->singledatelist;
  1144.     data->log_data->singledatelist = NULL;
  1145.     holddate = data->log_data->datelist;
  1146.     data->log_data->datelist = NULL;
  1147.     requested = log_version_requested (data->log_data, data->revlist,
  1148.                        data->rcs, vnode);
  1149.     data->log_data->singledatelist = holdsingle;
  1150.     data->log_data->datelist = holddate;
  1151.  
  1152.     if (requested)
  1153.     {
  1154.     struct datelist *d;
  1155.  
  1156.     /* For each single date, if this revision is before the
  1157.        specified date, but is closer than the previously selected
  1158.        revision, select it instead.  */
  1159.     for (d = data->log_data->singledatelist; d != NULL; d = d->next)
  1160.     {
  1161.         if (RCS_datecmp (vnode->date, d->end) <= 0
  1162.         && (d->start == NULL
  1163.             || RCS_datecmp (vnode->date, d->start) > 0))
  1164.         {
  1165.         if (d->start != NULL)
  1166.             free (d->start);
  1167.         d->start = xstrdup (vnode->date);
  1168.         }
  1169.     }
  1170.     }
  1171.  
  1172.     return 0;
  1173. }
  1174.  
  1175. /*
  1176.  * Count the number of revisions we are going to print.
  1177.  */
  1178. static int
  1179. log_count_print (p, closure)
  1180.     Node *p;
  1181.     void *closure;
  1182. {
  1183.     struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
  1184.     Node *pv;
  1185.  
  1186.     pv = findnode (data->rcs->versions, p->key);
  1187.     if (pv == NULL)
  1188.     error (1, 0, "missing version `%s' in RCS file `%s'",
  1189.            p->key, data->rcs->path);
  1190.     if (log_version_requested (data->log_data, data->revlist, data->rcs,
  1191.                    (RCSVers *) pv->data))
  1192.     return 1;
  1193.     else
  1194.     return 0;
  1195. }
  1196.  
  1197. /*
  1198.  * Print the list of changes, not including the trunk, in reverse
  1199.  * order for each branch.
  1200.  */
  1201. static void
  1202. log_tree (log_data, revlist, rcs, ver)
  1203.     struct log_data *log_data;
  1204.     struct revlist *revlist;
  1205.     RCSNode *rcs;
  1206.     const char *ver;
  1207. {
  1208.     Node *p;
  1209.     RCSVers *vnode;
  1210.  
  1211.     p = findnode (rcs->versions, ver);
  1212.     if (p == NULL)
  1213.     error (1, 0, "missing version `%s' in RCS file `%s'",
  1214.            ver, rcs->path);
  1215.     vnode = (RCSVers *) p->data;
  1216.     if (vnode->next != NULL)
  1217.     log_tree (log_data, revlist, rcs, vnode->next);
  1218.     if (vnode->branches != NULL)
  1219.     {
  1220.     Node *head, *branch;
  1221.  
  1222.     /* We need to do the branches in reverse order.  This breaks
  1223.            the List abstraction, but so does most of the branch
  1224.            manipulation in rcs.c.  */
  1225.     head = vnode->branches->list;
  1226.     for (branch = head->prev; branch != head; branch = branch->next)
  1227.     {
  1228.         log_abranch (log_data, revlist, rcs, branch->key);
  1229.         log_tree (log_data, revlist, rcs, branch->key);
  1230.     }
  1231.     }
  1232. }
  1233.  
  1234. /*
  1235.  * Log the changes for a branch, in reverse order.
  1236.  */
  1237. static void
  1238. log_abranch (log_data, revlist, rcs, ver)
  1239.     struct log_data *log_data;
  1240.     struct revlist *revlist;
  1241.     RCSNode *rcs;
  1242.     const char *ver;
  1243. {
  1244.     Node *p;
  1245.     RCSVers *vnode;
  1246.  
  1247.     p = findnode (rcs->versions, ver);
  1248.     if (p == NULL)
  1249.     error (1, 0, "missing version `%s' in RCS file `%s'",
  1250.            ver, rcs->path);
  1251.     vnode = (RCSVers *) p->data;
  1252.     if (vnode->next != NULL)
  1253.     log_abranch (log_data, revlist, rcs, vnode->next);
  1254.     log_version (log_data, revlist, rcs, vnode, 0);
  1255. }
  1256.  
  1257. /*
  1258.  * Print the log output for a single version.
  1259.  */
  1260. static void
  1261. log_version (log_data, revlist, rcs, ver, trunk)
  1262.     struct log_data *log_data;
  1263.     struct revlist *revlist;
  1264.     RCSNode *rcs;
  1265.     RCSVers *ver;
  1266.     int trunk;
  1267. {
  1268.     Node *p;
  1269.     int year, mon, mday, hour, min, sec;
  1270.     char buf[100];
  1271.     Node *padd, *pdel;
  1272.  
  1273.     if (! log_version_requested (log_data, revlist, rcs, ver))
  1274.     return;
  1275.  
  1276.     cvs_output ("----------------------------\nrevision ", 0);
  1277.     cvs_output (ver->version, 0);
  1278.  
  1279.     p = findnode (ver->other, ";locker");
  1280.     if (p != NULL)
  1281.     {
  1282.     cvs_output ("\tlocked by: ", 0);
  1283.     cvs_output (p->data, 0);
  1284.     cvs_output (";", 1);
  1285.     }
  1286.  
  1287.     cvs_output ("\ndate: ", 0);
  1288.     (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
  1289.            &sec);
  1290.     if (year < 1900)
  1291.     year += 1900;
  1292.     sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
  1293.          hour, min, sec);
  1294.     cvs_output (buf, 0);
  1295.  
  1296.     cvs_output (";  author: ", 0);
  1297.     cvs_output (ver->author, 0);
  1298.  
  1299.     cvs_output (";  state: ", 0);
  1300.     p = findnode (ver->other, "state");
  1301.     cvs_output (p->data, 0);
  1302.     cvs_output (";", 1);
  1303.  
  1304.     if (! trunk)
  1305.     {
  1306.     padd = findnode (ver->other, ";add");
  1307.     pdel = findnode (ver->other, ";delete");
  1308.     }
  1309.     else if (ver->next == NULL)
  1310.     {
  1311.     padd = NULL;
  1312.     pdel = NULL;
  1313.     }
  1314.     else
  1315.     {
  1316.     Node *nextp;
  1317.     RCSVers *nextver;
  1318.  
  1319.     nextp = findnode (rcs->versions, ver->next);
  1320.     if (nextp == NULL)
  1321.         error (1, 0, "missing version `%s' in `%s'", ver->next,
  1322.            rcs->path);
  1323.     nextver = (RCSVers *) nextp->data;
  1324.     pdel = findnode (nextver->other, ";add");
  1325.     padd = findnode (nextver->other, ";delete");
  1326.     }
  1327.  
  1328.     if (padd != NULL)
  1329.     {
  1330.     cvs_output ("  lines: +", 0);
  1331.     cvs_output (padd->data, 0);
  1332.     cvs_output (" -", 2);
  1333.     cvs_output (pdel->data, 0);
  1334.     }
  1335.  
  1336.     if (ver->branches != NULL)
  1337.     {
  1338.     cvs_output ("\nbranches:", 0);
  1339.     walklist (ver->branches, log_branch, (void *) NULL);
  1340.     }
  1341.  
  1342.     cvs_output ("\n", 1);
  1343.  
  1344.     p = findnode (ver->other, "log");
  1345.     if (p == NULL || p->data[0] == '\0')
  1346.     cvs_output ("*** empty log message ***\n", 0);
  1347.     else
  1348.     {
  1349.     /* FIXME: Technically, the log message could contain a null
  1350.            byte.  */
  1351.     cvs_output (p->data, 0);
  1352.     if (p->data[strlen (p->data) - 1] != '\n')
  1353.         cvs_output ("\n", 1);
  1354.     }
  1355. }
  1356.  
  1357. /*
  1358.  * Output a branch version.  This is called via walklist.
  1359.  */
  1360. /*ARGSUSED*/
  1361. static int
  1362. log_branch (p, closure)
  1363.     Node *p;
  1364.     void *closure;
  1365. {
  1366.     cvs_output ("  ", 2);
  1367.     if ((numdots (p->key) & 1) == 0)
  1368.     cvs_output (p->key, 0);
  1369.     else
  1370.     {
  1371.     char *f, *cp;
  1372.  
  1373.     f = xstrdup (p->key);
  1374.     cp = strrchr (f, '.');
  1375.     *cp = '\0';
  1376.     cvs_output (f, 0);
  1377.     free (f);
  1378.     }
  1379.     cvs_output (";", 1);
  1380.     return 0;
  1381. }
  1382.  
  1383. /*
  1384.  * Print a warm fuzzy message
  1385.  */
  1386. /* ARGSUSED */
  1387. static Dtype
  1388. log_dirproc (callerdat, dir, repository, update_dir, entries)
  1389.     void *callerdat;
  1390.     char *dir;
  1391.     char *repository;
  1392.     char *update_dir;
  1393.     List *entries;
  1394. {
  1395.     if (!isdir (dir))
  1396.     return (R_SKIP_ALL);
  1397.  
  1398.     if (!quiet)
  1399.     error (0, 0, "Logging %s", update_dir);
  1400.     return (R_PROCESS);
  1401. }
  1402.  
  1403. /*
  1404.  * Compare versions.  This is taken from RCS compartial.
  1405.  */
  1406. static int
  1407. version_compare (v1, v2, len)
  1408.     const char *v1;
  1409.     const char *v2;
  1410.     int len;
  1411. {
  1412.     while (1)
  1413.     {
  1414.     int d1, d2, r;
  1415.  
  1416.     if (*v1 == '\0')
  1417.         return 1;
  1418.     if (*v2 == '\0')
  1419.         return -1;
  1420.  
  1421.     while (*v1 == '0')
  1422.         ++v1;
  1423.     for (d1 = 0; isdigit (v1[d1]); ++d1)
  1424.         ;
  1425.  
  1426.     while (*v2 == '0')
  1427.         ++v2;
  1428.     for (d2 = 0; isdigit (v2[d2]); ++d2)
  1429.         ;
  1430.  
  1431.     if (d1 != d2)
  1432.         return d1 < d2 ? -1 : 1;
  1433.  
  1434.     r = memcmp (v1, v2, d1);
  1435.     if (r != 0)
  1436.         return r;
  1437.  
  1438.     --len;
  1439.     if (len == 0)
  1440.         return 0;
  1441.  
  1442.     v1 += d1;
  1443.     v2 += d1;
  1444.  
  1445.     if (*v1 == '.')
  1446.         ++v1;
  1447.     if (*v2 == '.')
  1448.         ++v2;
  1449.     }
  1450. }
  1451.