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 / rcs.c < prev    next >
C/C++ Source or Header  |  1996-09-28  |  75KB  |  3,076 lines

  1. /*
  2.  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  3.  * 
  4.  * You may distribute under the terms of the GNU General Public License as
  5.  * specified in the README file that comes with the CVS 1.4 kit.
  6.  * 
  7.  * The routines contained in this file do all the rcs file parsing and
  8.  * manipulation
  9.  */
  10.  
  11. #include <assert.h>
  12. #include "cvs.h"
  13.  
  14. static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile));
  15. static void RCS_reparsercsfile PROTO((RCSNode *, int, FILE **));
  16. static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch));
  17. static int getrcskey PROTO((FILE * fp, char **keyp, char **valp,
  18.                 size_t *lenp));
  19. static void getrcsrev PROTO ((FILE *fp, char **revp));
  20. static int checkmagic_proc PROTO((Node *p, void *closure));
  21. static void do_branches PROTO((List * list, char *val));
  22. static void do_symbols PROTO((List * list, char *val));
  23. static void rcsvers_delproc PROTO((Node * p));
  24. static char *translate_symtag PROTO((RCSNode *, const char *));
  25.  
  26. enum rcs_delta_op {RCS_ANNOTATE, RCS_FETCH};
  27. static void RCS_deltas PROTO ((RCSNode *, FILE *, char *, enum rcs_delta_op,
  28.                    char **, size_t *));
  29.  
  30. /*
  31.  * We don't want to use isspace() from the C library because:
  32.  *
  33.  * 1. The definition of "whitespace" in RCS files includes ASCII
  34.  *    backspace, but the C locale doesn't.
  35.  * 2. isspace is an very expensive function call in some implementations
  36.  *    due to the addition of wide character support.
  37.  */
  38. static const char spacetab[] = {
  39.         0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,    /* 0x00 - 0x0f */
  40.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
  41.         1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
  42.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
  43.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
  44.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
  45.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */
  46.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
  47.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
  48.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
  49.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
  50.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
  51.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
  52.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
  53.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
  54.         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0  /* 0xf0 - 0xff */
  55. };
  56.  
  57. #define whitespace(c)    (spacetab[(unsigned char)c] != 0)
  58.  
  59.  
  60. /*
  61.  * Parse an rcsfile given a user file name and a repository
  62.  */
  63. RCSNode *
  64. RCS_parse (file, repos)
  65.     const char *file;
  66.     const char *repos;
  67. {
  68.     RCSNode *rcs;
  69.     FILE *fp;
  70.     char rcsfile[PATH_MAX];
  71.  
  72.     (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
  73.     if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL) 
  74.     {
  75.         rcs = RCS_parsercsfile_i(fp, rcsfile);
  76.     if (rcs != NULL) 
  77.         rcs->flags |= VALID;
  78.  
  79.     fclose (fp);
  80.     return (rcs);
  81.     }
  82.     else if (! existence_error (errno))
  83.     {
  84.     error (0, errno, "cannot open %s", rcsfile);
  85.     return NULL;
  86.     }
  87.  
  88.     (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT);
  89.     if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) != NULL) 
  90.     {
  91.         rcs = RCS_parsercsfile_i(fp, rcsfile);
  92.     if (rcs != NULL)
  93.     {
  94.         rcs->flags |= INATTIC;
  95.         rcs->flags |= VALID;
  96.     }
  97.  
  98.     fclose (fp);
  99.     return (rcs);
  100.     }
  101.     else if (! existence_error (errno))
  102.     {
  103.     error (0, errno, "cannot open %s", rcsfile);
  104.     return NULL;
  105.     }
  106.  
  107.     return (NULL);
  108. }
  109.  
  110. /*
  111.  * Parse a specific rcsfile.
  112.  */
  113. RCSNode *
  114. RCS_parsercsfile (rcsfile)
  115.     char *rcsfile;
  116. {
  117.     FILE *fp;
  118.     RCSNode *rcs;
  119.  
  120.     /* open the rcsfile */
  121.     if ((fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ)) == NULL)
  122.     {
  123.     error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
  124.     return (NULL);
  125.     }
  126.  
  127.     rcs = RCS_parsercsfile_i (fp, rcsfile);
  128.  
  129.     fclose (fp);
  130.     return (rcs);
  131. }
  132.  
  133.  
  134. /*
  135.  */ 
  136. static RCSNode *
  137. RCS_parsercsfile_i (fp, rcsfile)
  138.     FILE *fp;
  139.     const char *rcsfile;
  140. {
  141.     RCSNode *rdata;
  142.     char *key, *value;
  143.  
  144.     /* make a node */
  145.     rdata = (RCSNode *) xmalloc (sizeof (RCSNode));
  146.     memset ((char *) rdata, 0, sizeof (RCSNode));
  147.     rdata->refcount = 1;
  148.     rdata->path = xstrdup (rcsfile);
  149.  
  150.     /* Process HEAD and BRANCH keywords from the RCS header.  
  151.      *
  152.      * Most cvs operatations on the main branch don't need any more
  153.      * information.  Those that do call XXX to completely parse the
  154.      * RCS file.  */
  155.  
  156.     if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
  157.     goto l_error;
  158.     if (strcmp (key, RCSDESC) == 0)
  159.     goto l_error;
  160.  
  161.     if (strcmp (RCSHEAD, key) == 0 && value != NULL)
  162.     rdata->head = xstrdup (value);
  163.  
  164.     if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL)
  165.     goto l_error;
  166.     if (strcmp (key, RCSDESC) == 0)
  167.     goto l_error;
  168.  
  169.     if (strcmp (RCSBRANCH, key) == 0 && value != NULL)
  170.     {
  171.     char *cp;
  172.  
  173.     rdata->branch = xstrdup (value);
  174.     if ((numdots (rdata->branch) & 1) != 0)
  175.     {
  176.         /* turn it into a branch if it's a revision */
  177.         cp = strrchr (rdata->branch, '.');
  178.         *cp = '\0';
  179.     }
  180.     }
  181.  
  182.     rdata->flags |= PARTIAL;
  183.     return rdata;
  184.  
  185. l_error:
  186.     if (!really_quiet)
  187.     {
  188.     if (ferror(fp))
  189.     {
  190.         error (1, 0, "error reading `%s'", rcsfile);
  191.     }
  192.     else
  193.     {
  194.         error (0, 0, "`%s' does not appear to be a valid rcs file",
  195.            rcsfile);
  196.     }
  197.     }
  198.     freercsnode (&rdata);
  199.     return (NULL);
  200. }
  201.  
  202.  
  203. /* Do the real work of parsing an RCS file.
  204.  
  205.    On error, die with a fatal error; if it returns at all it was successful.
  206.  
  207.    If ALL is nonzero, remember all keywords and values.  Otherwise
  208.    only keep the ones we will need.
  209.  
  210.    If PFP is NULL, close the file when done.  Otherwise, leave it open
  211.    and store the FILE * in *PFP.  */
  212. static void
  213. RCS_reparsercsfile (rdata, all, pfp)
  214.     RCSNode *rdata;
  215.     int all;
  216.     FILE **pfp;
  217. {
  218.     FILE *fp;
  219.     char *rcsfile;
  220.  
  221.     Node *q;
  222.     RCSVers *vnode;
  223.     int n;
  224.     char *cp;
  225.     char *key, *value;
  226.  
  227.     assert (rdata != NULL);
  228.     rcsfile = rdata->path;
  229.  
  230.     fp = CVS_FOPEN (rcsfile, FOPEN_BINARY_READ);
  231.     if (fp == NULL)
  232.     error (1, 0, "unable to reopen `%s'", rcsfile);
  233.  
  234.     /* make a node */
  235.     rdata->versions = getlist ();
  236.  
  237.     /*
  238.      * process all the special header information, break out when we get to
  239.      * the first revision delta
  240.      */
  241.     for (;;)
  242.     {
  243.     /* get the next key/value pair */
  244.  
  245.     /* if key is NULL here, then the file is missing some headers
  246.        or we had trouble reading the file. */
  247.     if (getrcskey (fp, &key, &value, NULL) == -1 || key == NULL
  248.         || strcmp (key, RCSDESC) == 0)
  249.     {
  250.         if (ferror(fp))
  251.         {
  252.         error (1, 0, "error reading `%s'", rcsfile);
  253.         }
  254.         else
  255.         {
  256.         error (1, 0, "`%s' does not appear to be a valid rcs file",
  257.                rcsfile);
  258.         }
  259.     }
  260.  
  261.     if (strcmp (RCSSYMBOLS, key) == 0)
  262.     {
  263.         if (value != NULL)
  264.         {
  265.         rdata->symbols_data = xstrdup(value);
  266.         continue;
  267.         }
  268.     }
  269.  
  270.     if (strcmp (RCSEXPAND, key) == 0)
  271.     {
  272.         rdata->expand = xstrdup (value);
  273.         continue;
  274.     }
  275.  
  276.     /*
  277.      * check key for '.''s and digits (probably a rev) if it is a
  278.      * revision, we are done with the headers and are down to the
  279.      * revision deltas, so we break out of the loop
  280.      */
  281.     for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
  282.          /* do nothing */ ;
  283.     if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
  284.         break;
  285.  
  286.     if (all)
  287.     {
  288.         Node *kv;
  289.  
  290.         if (rdata->other == NULL)
  291.         rdata->other = getlist ();
  292.         kv = getnode ();
  293.         kv->type = RCSFIELD;
  294.         kv->key = xstrdup (key);
  295.         kv->data = xstrdup (value);
  296.         if (addnode (rdata->other, kv) != 0)
  297.         {
  298.         error (0, 0, "warning: duplicate key `%s' in RCS file `%s'",
  299.                key, rcsfile);
  300.         freenode (kv);
  301.         }
  302.     }
  303.  
  304.     /* if we haven't grabbed it yet, we didn't want it */
  305.     }
  306.  
  307.     /*
  308.      * we got out of the loop, so we have the first part of the first
  309.      * revision delta in our hand key=the revision and value=the date key and
  310.      * its value
  311.      */
  312.     for (;;)
  313.     {
  314.     char *valp;
  315.     Node *kvstate;
  316.  
  317.         vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
  318.     memset (vnode, 0, sizeof (RCSVers));
  319.  
  320.     /* fill in the version before we forget it */
  321.     vnode->version = xstrdup (key);
  322.  
  323.     /* grab the value of the date from value */
  324.     valp = value + strlen (RCSDATE);/* skip the "date" keyword */
  325.     while (whitespace (*valp))        /* take space off front of value */
  326.         valp++;
  327.  
  328.     vnode->date = xstrdup (valp);
  329.  
  330.     /* Get author field.  */
  331.     (void) getrcskey (fp, &key, &value, NULL);
  332.     /* FIXME: should be using errno in case of ferror.  */
  333.     if (key == NULL || strcmp (key, "author") != 0)
  334.         error (1, 0, "\
  335. unable to parse rcs file; `author' not in the expected place");
  336.     vnode->author = xstrdup (value);
  337.  
  338.     /* Get state field.  */
  339.     (void) getrcskey (fp, &key, &value, NULL);
  340.     /* FIXME: should be using errno in case of ferror.  */
  341.     if (key == NULL || strcmp (key, "state") != 0)
  342.         error (1, 0, "\
  343. unable to parse rcs file; `state' not in the expected place");
  344.     if (strcmp (value, "dead") == 0)
  345.     {
  346.         vnode->dead = 1;
  347.     }
  348.     if (! all)
  349.         kvstate = NULL;
  350.     else
  351.     {
  352.         if (vnode->other == NULL)
  353.         vnode->other = getlist ();
  354.         kvstate = getnode ();
  355.         kvstate->type = RCSFIELD;
  356.         kvstate->key = xstrdup (key);
  357.         kvstate->data = xstrdup (value);
  358.         if (addnode (vnode->other, kvstate) != 0)
  359.         {
  360.         error (0, 0,
  361.                "\
  362. warning: duplicate key `%s' in version `%s' of RCS file `%s'",
  363.                key, vnode->version, rcsfile);
  364.         freenode (kvstate);
  365.         kvstate = NULL;
  366.         }
  367.     }
  368.  
  369.     /* fill in the branch list (if any branches exist) */
  370.     (void) getrcskey (fp, &key, &value, NULL);
  371.     /* FIXME: should be handling various error conditions better.  */
  372.     if (key != NULL && strcmp (key, RCSDESC) == 0)
  373.         value = NULL;
  374.     if (value != (char *) NULL)
  375.     {
  376.         vnode->branches = getlist ();
  377.         do_branches (vnode->branches, value);
  378.     }
  379.  
  380.     /* fill in the next field if there is a next revision */
  381.     (void) getrcskey (fp, &key, &value, NULL);
  382.     /* FIXME: should be handling various error conditions better.  */
  383.     if (key != NULL && strcmp (key, RCSDESC) == 0)
  384.         value = NULL;
  385.     if (value != (char *) NULL)
  386.         vnode->next = xstrdup (value);
  387.  
  388.     /*
  389.      * at this point, we skip any user defined fields XXX - this is where
  390.      * we put the symbolic link stuff???
  391.      */
  392.     /* FIXME: Does not correctly handle errors, e.g. from stdio.  */
  393.     while ((n = getrcskey (fp, &key, &value, NULL)) >= 0)
  394.     {
  395.         assert (key != NULL);
  396.  
  397.         if (strcmp (key, RCSDESC) == 0)
  398.         {
  399.         n = -1;
  400.         break;
  401.         }
  402.  
  403.         /* Enable use of repositories created by certain obsolete
  404.            versions of CVS.  This code should remain indefinately;
  405.            there is no procedure for converting old repositories, and
  406.            checking for it is harmless.  */
  407.         if (strcmp(key, RCSDEAD) == 0)
  408.         {
  409.         vnode->dead = 1;
  410.         if (kvstate != NULL)
  411.         {
  412.             free (kvstate->data);
  413.             kvstate->data = xstrdup ("dead");
  414.         }
  415.         continue;
  416.         }
  417.         /* if we have a revision, break and do it */
  418.         for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
  419.          /* do nothing */ ;
  420.         if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
  421.         break;
  422.  
  423.         if (all)
  424.         {
  425.         Node *kv;
  426.  
  427.         if (vnode->other == NULL)
  428.             vnode->other = getlist ();
  429.         kv = getnode ();
  430.         kv->type = RCSFIELD;
  431.         kv->key = xstrdup (key);
  432.         kv->data = xstrdup (value);
  433.         if (addnode (vnode->other, kv) != 0)
  434.         {
  435.             error (0, 0,
  436.                "\
  437. warning: duplicate key `%s' in version `%s' of RCS file `%s'",
  438.                key, vnode->version, rcsfile);
  439.             freenode (kv);
  440.         }
  441.         }
  442.     }
  443.  
  444.     /* get the node */
  445.     q = getnode ();
  446.     q->type = RCSVERS;
  447.     q->delproc = rcsvers_delproc;
  448.     q->data = (char *) vnode;
  449.     q->key = vnode->version;
  450.  
  451.     /* add the nodes to the list */
  452.     if (addnode (rdata->versions, q) != 0)
  453.     {
  454. #if 0
  455.         purify_printf("WARNING: Adding duplicate version: %s (%s)\n",
  456.              q->key, rcsfile);
  457.         freenode (q);
  458. #endif
  459.     }
  460.  
  461.     /*
  462.      * if we left the loop because there were no more keys, we break out
  463.      * of the revision processing loop
  464.      */
  465.     if (n < 0)
  466.         break;
  467.     }
  468.  
  469.     if (all && key != NULL && strcmp (key, RCSDESC) == 0)
  470.     {
  471.     Node *kv;
  472.  
  473.     if (vnode->other == NULL)
  474.         vnode->other = getlist ();
  475.     kv = getnode ();
  476.     kv->type = RCSFIELD;
  477.     kv->key = xstrdup (key);
  478.     kv->data = xstrdup (value);
  479.     if (addnode (vnode->other, kv) != 0)
  480.     {
  481.         error (0, 0,
  482.            "warning: duplicate key `%s' in RCS file `%s'",
  483.            key, rcsfile);
  484.         freenode (kv);
  485.     }
  486.     }
  487.  
  488.     rdata->delta_pos = ftell (fp);
  489.  
  490.     if (pfp == NULL)
  491.     {
  492.     if (fclose (fp) < 0)
  493.         error (0, errno, "cannot close %s", rcsfile);
  494.     }
  495.     else
  496.     {
  497.     *pfp = fp;
  498.     }
  499.     rdata->flags &= ~PARTIAL;
  500. }
  501.  
  502. /*
  503.  * Fully parse the RCS file.  Store all keyword/value pairs, fetch the
  504.  * log messages for each revision, and fetch add and delete counts for
  505.  * each revision (we could fetch the entire text for each revision,
  506.  * but the only caller, log_fileproc, doesn't need that information,
  507.  * so we don't waste the memory required to store it).  The add and
  508.  * delete counts are stored on the OTHER field of the RCSVERSNODE
  509.  * structure, under the names ";add" and ";delete", so that we don't
  510.  * waste the memory space of extra fields in RCSVERSNODE for code
  511.  * which doesn't need this information.
  512.  */
  513.  
  514. void
  515. RCS_fully_parse (rcs)
  516.     RCSNode *rcs;
  517. {
  518.     FILE *fp;
  519.  
  520.     RCS_reparsercsfile (rcs, 1, &fp);
  521.  
  522.     while (1)
  523.     {
  524.     int c;
  525.     char *key, *value;
  526.     size_t vallen;
  527.     Node *vers;
  528.     RCSVers *vnode;
  529.  
  530.     /* Rather than try to keep track of how much information we
  531.            have read, just read to the end of the file.  */
  532.     do
  533.     {
  534.         c = getc (fp);
  535.         if (c == EOF)
  536.         break;
  537.     } while (whitespace (c));
  538.     if (c == EOF)
  539.         break;
  540.     if (ungetc (c, fp) == EOF)
  541.         error (1, errno, "ungetc failed");
  542.  
  543.     getrcsrev (fp, &key);
  544.     vers = findnode (rcs->versions, key);
  545.     if (vers == NULL)
  546.         error (1, 0,
  547.            "mismatch in rcs file %s between deltas and deltatexts",
  548.            rcs->path);
  549.  
  550.     vnode = (RCSVers *) vers->data;
  551.  
  552.     while (getrcskey (fp, &key, &value, &vallen) >= 0)
  553.     {
  554.         if (strcmp (key, "text") != 0)
  555.         {
  556.         Node *kv;
  557.  
  558.         if (vnode->other == NULL)
  559.             vnode->other = getlist ();
  560.         kv = getnode ();
  561.         kv->type = RCSFIELD;
  562.         kv->key = xstrdup (key);
  563.         kv->data = xstrdup (value);
  564.         if (addnode (vnode->other, kv) != 0)
  565.         {
  566.             error (0, 0,
  567.                "\
  568. warning: duplicate key `%s' in version `%s' of RCS file `%s'",
  569.                key, vnode->version, rcs->path);
  570.             freenode (kv);
  571.         }
  572.  
  573.         continue;
  574.         }
  575.  
  576.         if (strcmp (vnode->version, rcs->head) != 0)
  577.         {
  578.         unsigned long add, del;
  579.         char buf[50];
  580.         Node *kv;
  581.  
  582.         /* This is a change text.  Store the add and delete
  583.                    counts.  */
  584.         add = 0;
  585.         del = 0;
  586.         if (value != NULL)
  587.         {
  588.             const char *cp;
  589.  
  590.             cp = value;
  591.             while (cp < value + vallen)
  592.             {
  593.             char op;
  594.             unsigned long count;
  595.  
  596.             op = *cp++;
  597.             if (op != 'a' && op  != 'd')
  598.                 error (1, 0, "unrecognized operation '%c' in %s",
  599.                    op, rcs->path);
  600.             (void) strtoul (cp, (char **) &cp, 10);
  601.             if (*cp++ != ' ')
  602.                 error (1, 0, "space expected in %s",
  603.                    rcs->path);
  604.             count = strtoul (cp, (char **) &cp, 10);
  605.             if (*cp++ != '\012')
  606.                 error (1, 0, "linefeed expected in %s",
  607.                    rcs->path);
  608.  
  609.             if (op == 'd')
  610.                 del += count;
  611.             else
  612.             {
  613.                 add += count;
  614.                 while (count != 0)
  615.                 {
  616.                 if (*cp == '\012')
  617.                     --count;
  618.                 else if (cp == value + vallen)
  619.                 {
  620.                     if (count != 1)
  621.                     error (1, 0, "\
  622. invalid rcs file %s: premature end of value",
  623.                            rcs->path);
  624.                     else
  625.                     break;
  626.                 }
  627.                 ++cp;
  628.                 }
  629.             }
  630.             }
  631.         }
  632.  
  633.         sprintf (buf, "%lu", add);
  634.         kv = getnode ();
  635.         kv->type = RCSFIELD;
  636.         kv->key = xstrdup (";add");
  637.         kv->data = xstrdup (buf);
  638.         if (addnode (vnode->other, kv) != 0)
  639.         {
  640.             error (0, 0,
  641.                "\
  642. warning: duplicate key `%s' in version `%s' of RCS file `%s'",
  643.                key, vnode->version, rcs->path);
  644.             freenode (kv);
  645.         }
  646.  
  647.         sprintf (buf, "%lu", del);
  648.         kv = getnode ();
  649.         kv->type = RCSFIELD;
  650.         kv->key = xstrdup (";delete");
  651.         kv->data = xstrdup (buf);
  652.         if (addnode (vnode->other, kv) != 0)
  653.         {
  654.             error (0, 0,
  655.                "\
  656. warning: duplicate key `%s' in version `%s' of RCS file `%s'",
  657.                key, vnode->version, rcs->path);
  658.             freenode (kv);
  659.         }
  660.         }
  661.  
  662.         /* We have found the "text" key which ends the data for
  663.                this revision.  Break out of the loop and go on to the
  664.                next revision.  */
  665.         break;
  666.     }
  667.     }
  668.  
  669.     if (fclose (fp) < 0)
  670.     error (0, errno, "cannot close %s", rcs->path);
  671. }
  672.  
  673. /*
  674.  * freercsnode - free up the info for an RCSNode
  675.  */
  676. void
  677. freercsnode (rnodep)
  678.     RCSNode **rnodep;
  679. {
  680.     if (rnodep == NULL || *rnodep == NULL)
  681.     return;
  682.  
  683.     ((*rnodep)->refcount)--;
  684.     if ((*rnodep)->refcount != 0)
  685.     {
  686.     *rnodep = (RCSNode *) NULL;
  687.     return;
  688.     }
  689.     free ((*rnodep)->path);
  690.     dellist (&(*rnodep)->versions);
  691.     if ((*rnodep)->symbols != (List *) NULL)
  692.     dellist (&(*rnodep)->symbols);
  693.     if ((*rnodep)->symbols_data != (char *) NULL)
  694.     free ((*rnodep)->symbols_data);
  695.     if ((*rnodep)->expand != NULL)
  696.     free ((*rnodep)->expand);
  697.     if ((*rnodep)->head != (char *) NULL)
  698.     free ((*rnodep)->head);
  699.     if ((*rnodep)->branch != (char *) NULL)
  700.     free ((*rnodep)->branch);
  701.     if ((*rnodep)->other != (List *) NULL)
  702.     dellist (&(*rnodep)->other);
  703.     free ((char *) *rnodep);
  704.     *rnodep = (RCSNode *) NULL;
  705. }
  706.  
  707. /*
  708.  * rcsvers_delproc - free up an RCSVers type node
  709.  */
  710. static void
  711. rcsvers_delproc (p)
  712.     Node *p;
  713. {
  714.     RCSVers *rnode;
  715.  
  716.     rnode = (RCSVers *) p->data;
  717.  
  718.     if (rnode->branches != (List *) NULL)
  719.     dellist (&rnode->branches);
  720.     if (rnode->date != (char *) NULL)
  721.     free (rnode->date);
  722.     if (rnode->next != (char *) NULL)
  723.     free (rnode->next);
  724.     if (rnode->author != (char *) NULL)
  725.     free (rnode->author);
  726.     if (rnode->other != (List *) NULL)
  727.     dellist (&rnode->other);
  728.     free ((char *) rnode);
  729. }
  730.  
  731. /*
  732.  * getrcskey - fill in the key and value from the rcs file the algorithm is
  733.  *             as follows 
  734.  *
  735.  *    o skip whitespace o fill in key with everything up to next white 
  736.  *      space or semicolon 
  737.  *    o if key == "desc" then key and data are NULL and return -1 
  738.  *    o if key wasn't terminated by a semicolon, skip white space and fill 
  739.  *      in value with everything up to a semicolon 
  740.  *    o compress all whitespace down to a single space 
  741.  *    o if a word starts with @, do funky rcs processing
  742.  *    o strip whitespace off end of value or set value to NULL if it empty 
  743.  *    o return 0 since we found something besides "desc"
  744.  *
  745.  * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey
  746.  * function; the contents are only valid until the next call to
  747.  * getrcskey or getrcsrev.  If LENP is not NULL, this sets *LENP to
  748.  * the length of *VALUEP; this is needed if the string might contain
  749.  * binary data.
  750.  */
  751.  
  752. static char *key = NULL;
  753. static char *value = NULL;
  754. static size_t keysize = 0;
  755. static size_t valsize = 0;
  756.  
  757. #define ALLOCINCR 1024
  758.  
  759. static int
  760. getrcskey (fp, keyp, valp, lenp)
  761.     FILE *fp;
  762.     char **keyp;
  763.     char **valp;
  764.     size_t *lenp;
  765. {
  766.     char *cur, *max;
  767.     int c;
  768.     int just_string;
  769.  
  770.     if (lenp != NULL)
  771.         *lenp = 0;
  772.  
  773.     /* skip leading whitespace */
  774.     do
  775.     {
  776.     c = getc (fp);
  777.     if (c == EOF)
  778.     {
  779.         *keyp = (char *) NULL;
  780.         *valp = (char *) NULL;
  781.         return (-1);
  782.     }
  783.     } while (whitespace (c));
  784.  
  785.     /* fill in key */
  786.     cur = key;
  787.     max = key + keysize;
  788.     while (!whitespace (c) && c != ';')
  789.     {
  790.     if (cur >= max)
  791.     {
  792.         key = xrealloc (key, keysize + ALLOCINCR);
  793.         cur = key + keysize;
  794.         keysize += ALLOCINCR;
  795.         max = key + keysize;
  796.     }
  797.     *cur++ = c;
  798.  
  799.     c = getc (fp);
  800.     if (c == EOF)
  801.     {
  802.         *keyp = (char *) NULL;
  803.         *valp = (char *) NULL;
  804.         return (-1);
  805.     }
  806.     }
  807.     if (cur >= max)
  808.     {
  809.     key = xrealloc (key, keysize + ALLOCINCR);
  810.     cur = key + keysize;
  811.     keysize += ALLOCINCR;
  812.     max = key + keysize;
  813.     }
  814.     *cur = '\0';
  815.  
  816.     /* skip whitespace between key and val */
  817.     while (whitespace (c))
  818.     {
  819.     c = getc (fp);
  820.     if (c == EOF)
  821.     {
  822.         *keyp = (char *) NULL;
  823.         *valp = (char *) NULL;
  824.         return (-1);
  825.     }
  826.     } 
  827.  
  828.     /* if we ended key with a semicolon, there is no value */
  829.     if (c == ';')
  830.     {
  831.     *keyp = key;
  832.     *valp = (char *) NULL;
  833.     return (0);
  834.     }
  835.  
  836.     /* otherwise, there might be a value, so fill it in */
  837.     cur = value;
  838.     max = value + valsize;
  839.  
  840.     just_string = (strcmp (key, RCSDESC) == 0
  841.            || strcmp (key, "text") == 0
  842.            || strcmp (key, "log") == 0);
  843.  
  844.     /* process the value */
  845.     for (;;)
  846.     {
  847.     /* handle RCS "strings" */
  848.     if (c == '@') 
  849.     {
  850.         for (;;)
  851.         {
  852.         c = getc (fp);
  853.         if (c == EOF)
  854.         {
  855.             *keyp = (char *) NULL;
  856.             *valp = (char *) NULL;
  857.             return (-1);
  858.         }
  859.  
  860.         if (c == '@')
  861.         {
  862.             c = getc (fp);
  863.             if (c == EOF)
  864.             {
  865.             *keyp = (char *) NULL;
  866.             *valp = (char *) NULL;
  867.             return (-1);
  868.             }
  869.             
  870.             if (c != '@')
  871.             break;
  872.         }
  873.  
  874.         if (cur >= max)
  875.         {
  876.             value = xrealloc (value, valsize + ALLOCINCR);
  877.             cur = value + valsize;
  878.             valsize += ALLOCINCR;
  879.             max = value + valsize;
  880.         }
  881.         *cur++ = c;
  882.         }
  883.     }
  884.  
  885.     /* The syntax for some key-value pairs is different; they
  886.        don't end with a semicolon.  */
  887.     if (just_string)
  888.         break;
  889.  
  890.     /* compress whitespace down to a single space */
  891.     if (whitespace (c))
  892.     {
  893.         do {
  894.         c = getc (fp);
  895.         if (c == EOF)
  896.         {
  897.             *keyp = (char *) NULL;
  898.             *valp = (char *) NULL;
  899.             return (-1);
  900.         }
  901.         } while (whitespace (c));
  902.  
  903.         if (cur >= max)
  904.         {
  905.         value = xrealloc (value, valsize + ALLOCINCR);
  906.         cur = value + valsize;
  907.         valsize += ALLOCINCR;
  908.         max = value + valsize;
  909.         }
  910.         *cur++ = ' ';
  911.     }
  912.  
  913.     /* if we got a semi-colon we are done with the entire value */
  914.     if (c == ';')
  915.         break;
  916.  
  917.     if (cur >= max)
  918.     {
  919.         value = xrealloc (value, valsize + ALLOCINCR);
  920.         cur = value + valsize;
  921.         valsize += ALLOCINCR;
  922.         max = value + valsize;
  923.     }
  924.     *cur++ = c;
  925.  
  926.     c = getc (fp);
  927.     if (c == EOF)
  928.     {
  929.         *keyp = (char *) NULL;
  930.         *valp = (char *) NULL;
  931.         return (-1);
  932.     }
  933.     }
  934.  
  935.     /* terminate the string */
  936.     if (cur >= max)
  937.     {
  938.     value = xrealloc (value, valsize + ALLOCINCR);
  939.     cur = value + valsize;
  940.     valsize += ALLOCINCR;
  941.     max = value + valsize;
  942.     }
  943.     *cur = '\0';
  944.  
  945.     /* if the string is empty, make it null */
  946.     if (value && cur != value)
  947.     {
  948.     *valp = value;
  949.     if (lenp != NULL)
  950.         *lenp = cur - value;
  951.     }
  952.     else
  953.     *valp = NULL;
  954.     *keyp = key;
  955.     return (0);
  956. }
  957.  
  958. /* Read an RCS revision number from FP.  Put a pointer to it in *REVP;
  959.    it points to space managed by getrcsrev which is only good until
  960.    the next call to getrcskey or getrcsrev.  */
  961. static void
  962. getrcsrev (fp, revp)
  963.     FILE *fp;
  964.     char **revp;
  965. {
  966.     char *cur;
  967.     char *max;
  968.     int c;
  969.  
  970.     do {
  971.     c = getc (fp);
  972.     if (c == EOF)
  973.         /* FIXME: should be including filename in error message.  */
  974.         error (1, errno, "cannot read rcs file");
  975.     } while (whitespace (c));
  976.  
  977.     if (!(isdigit (c) || c == '.'))
  978.     /* FIXME: should be including filename in error message.  */
  979.     error (1, 0, "error reading rcs file; revision number expected");
  980.  
  981.     cur = key;
  982.     max = key + keysize;
  983.     while (isdigit (c) || c == '.')
  984.     {
  985.     if (cur >= max)
  986.     {
  987.         key = xrealloc (key, keysize + ALLOCINCR);
  988.         cur = key + keysize;
  989.         keysize += ALLOCINCR;
  990.         max = key + keysize;
  991.     }
  992.     *cur++ = c;
  993.  
  994.     c = getc (fp);
  995.     if (c == EOF)
  996.     {
  997.         /* FIXME: should be including filename in error message.  */
  998.         error (1, errno, "cannot read rcs file");
  999.     }
  1000.     }
  1001.  
  1002.     if (cur >= max)
  1003.     {
  1004.     key = xrealloc (key, keysize + ALLOCINCR);
  1005.     cur = key + keysize;
  1006.     keysize += ALLOCINCR;
  1007.     max = key + keysize;
  1008.     }
  1009.     *cur = '\0';
  1010.     *revp = key;
  1011. }
  1012.  
  1013. /*
  1014.  * process the symbols list of the rcs file
  1015.  */
  1016. static void
  1017. do_symbols (list, val)
  1018.     List *list;
  1019.     char *val;
  1020. {
  1021.     Node *p;
  1022.     char *cp = val;
  1023.     char *tag, *rev;
  1024.  
  1025.     for (;;)
  1026.     {
  1027.     /* skip leading whitespace */
  1028.     while (whitespace (*cp))
  1029.         cp++;
  1030.  
  1031.     /* if we got to the end, we are done */
  1032.     if (*cp == '\0')
  1033.         break;
  1034.  
  1035.     /* split it up into tag and rev */
  1036.     tag = cp;
  1037.     cp = strchr (cp, ':');
  1038.     *cp++ = '\0';
  1039.     rev = cp;
  1040.     while (!whitespace (*cp) && *cp != '\0')
  1041.         cp++;
  1042.     if (*cp != '\0')
  1043.         *cp++ = '\0';
  1044.  
  1045.     /* make a new node and add it to the list */
  1046.     p = getnode ();
  1047.     p->key = xstrdup (tag);
  1048.     p->data = xstrdup (rev);
  1049.     (void) addnode (list, p);
  1050.     }
  1051. }
  1052.  
  1053. /*
  1054.  * process the branches list of a revision delta
  1055.  */
  1056. static void
  1057. do_branches (list, val)
  1058.     List *list;
  1059.     char *val;
  1060. {
  1061.     Node *p;
  1062.     char *cp = val;
  1063.     char *branch;
  1064.  
  1065.     for (;;)
  1066.     {
  1067.     /* skip leading whitespace */
  1068.     while (whitespace (*cp))
  1069.         cp++;
  1070.  
  1071.     /* if we got to the end, we are done */
  1072.     if (*cp == '\0')
  1073.         break;
  1074.  
  1075.     /* find the end of this branch */
  1076.     branch = cp;
  1077.     while (!whitespace (*cp) && *cp != '\0')
  1078.         cp++;
  1079.     if (*cp != '\0')
  1080.         *cp++ = '\0';
  1081.  
  1082.     /* make a new node and add it to the list */
  1083.     p = getnode ();
  1084.     p->key = xstrdup (branch);
  1085.     (void) addnode (list, p);
  1086.     }
  1087. }
  1088.  
  1089. /*
  1090.  * Version Number
  1091.  * 
  1092.  * Returns the requested version number of the RCS file, satisfying tags and/or
  1093.  * dates, and walking branches, if necessary.
  1094.  * 
  1095.  * The result is returned; null-string if error.
  1096.  */
  1097. char *
  1098. RCS_getversion (rcs, tag, date, force_tag_match, return_both)
  1099.     RCSNode *rcs;
  1100.     char *tag;
  1101.     char *date;
  1102.     int force_tag_match;
  1103.     int return_both;
  1104. {
  1105.     /* make sure we have something to look at... */
  1106.     assert (rcs != NULL);
  1107.  
  1108.     if (tag && date)
  1109.     {
  1110.     char *branch, *rev;
  1111.  
  1112.     if (! RCS_isbranch (rcs, tag))
  1113.     {
  1114.         /* We can't get a particular date if the tag is not a
  1115.                branch.  */
  1116.         return NULL;
  1117.     }
  1118.  
  1119.     /* Work out the branch.  */
  1120.     if (! isdigit (tag[0]))
  1121.         branch = RCS_whatbranch (rcs, tag);
  1122.     else
  1123.         branch = xstrdup (tag);
  1124.  
  1125.     /* Fetch the revision of branch as of date.  */
  1126.     rev = RCS_getdatebranch (rcs, date, branch);
  1127.     free (branch);
  1128.     return (rev);
  1129.     }
  1130.     else if (tag)
  1131.     return (RCS_gettag (rcs, tag, force_tag_match, return_both));
  1132.     else if (date)
  1133.     return (RCS_getdate (rcs, date, force_tag_match));
  1134.     else
  1135.     return (RCS_head (rcs));
  1136.  
  1137. }
  1138.  
  1139. /*
  1140.  * Find the revision for a specific tag.
  1141.  * If force_tag_match is set, return NULL if an exact match is not
  1142.  * possible otherwise return RCS_head ().  We are careful to look for
  1143.  * and handle "magic" revisions specially.
  1144.  * 
  1145.  * If the matched tag is a branch tag, find the head of the branch.
  1146.  */
  1147. char *
  1148. RCS_gettag (rcs, symtag, force_tag_match, return_both)
  1149.     RCSNode *rcs;
  1150.     char *symtag;
  1151.     int force_tag_match;
  1152.     int return_both;
  1153. {
  1154.     char *tag = symtag;
  1155.     int tag_allocated = 0;
  1156.  
  1157.     /* make sure we have something to look at... */
  1158.     assert (rcs != NULL);
  1159.  
  1160.     /* XXX this is probably not necessary, --jtc */
  1161.     if (rcs->flags & PARTIAL) 
  1162.     RCS_reparsercsfile (rcs, 0, NULL);
  1163.  
  1164.     /* If tag is "HEAD", special case to get head RCS revision */
  1165.     if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0'))
  1166. #if 0 /* This #if 0 is only in the Cygnus code.  Why?  Death support?  */
  1167.     if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
  1168.         return ((char *) NULL);    /* head request for removed file */
  1169.     else
  1170. #endif
  1171.         return (RCS_head (rcs));
  1172.  
  1173.     if (!isdigit (tag[0]))
  1174.     {
  1175.     char *version;
  1176.  
  1177.     /* If we got a symbolic tag, resolve it to a numeric */
  1178.     version = translate_symtag (rcs, tag);
  1179.     if (version != NULL)
  1180.     {
  1181.         int dots;
  1182.         char *magic, *branch, *cp;
  1183.  
  1184.         tag = version;
  1185.         tag_allocated = 1;
  1186.  
  1187.         /*
  1188.          * If this is a magic revision, we turn it into either its
  1189.          * physical branch equivalent (if one exists) or into
  1190.          * its base revision, which we assume exists.
  1191.          */
  1192.         dots = numdots (tag);
  1193.         if (dots > 2 && (dots & 1) != 0)
  1194.         {
  1195.         branch = strrchr (tag, '.');
  1196.         cp = branch++ - 1;
  1197.         while (*cp != '.')
  1198.             cp--;
  1199.  
  1200.         /* see if we have .magic-branch. (".0.") */
  1201.         magic = xmalloc (strlen (tag) + 1);
  1202.         (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
  1203.         if (strncmp (magic, cp, strlen (magic)) == 0)
  1204.         {
  1205.             /* it's magic.  See if the branch exists */
  1206.             *cp = '\0';        /* turn it into a revision */
  1207.             (void) sprintf (magic, "%s.%s", tag, branch);
  1208.             branch = RCS_getbranch (rcs, magic, 1);
  1209.             free (magic);
  1210.             if (branch != NULL)
  1211.             {
  1212.             free (tag);
  1213.             return (branch);
  1214.             }
  1215.             return (tag);
  1216.         }
  1217.         free (magic);
  1218.         }
  1219.     }
  1220.     else
  1221.     {
  1222.         /* The tag wasn't there, so return the head or NULL */
  1223.         if (force_tag_match)
  1224.         return (NULL);
  1225.         else
  1226.         return (RCS_head (rcs));
  1227.     }
  1228.     }
  1229.  
  1230.     /*
  1231.      * numeric tag processing:
  1232.      *        1) revision number - just return it
  1233.      *        2) branch number   - find head of branch
  1234.      */
  1235.  
  1236.     /* strip trailing dots */
  1237.     while (tag[strlen (tag) - 1] == '.')
  1238.     tag[strlen (tag) - 1] = '\0';
  1239.  
  1240.     if ((numdots (tag) & 1) == 0)
  1241.     {
  1242.     char *branch;
  1243.  
  1244.     /* we have a branch tag, so we need to walk the branch */
  1245.     branch = RCS_getbranch (rcs, tag, force_tag_match);
  1246.     if (tag_allocated)
  1247.         free (tag);
  1248.     return branch;
  1249.     }
  1250.     else
  1251.     {
  1252.     Node *p;
  1253.  
  1254.     /* we have a revision tag, so make sure it exists */
  1255.     p = findnode (rcs->versions, tag);
  1256.     if (p != NULL)
  1257.     {
  1258.         /*
  1259.          * we have found a numeric revision for the revision tag.
  1260.          * To support expanding the RCS keyword Name, return both
  1261.          * the numeric tag and the supplied tag (which might be
  1262.          * symbolic).  They are separated with a ':' which is not
  1263.          * a valid tag char.  The variable return_both is only set
  1264.          * if this function is called through Version_TS ->
  1265.          * RCS_getversion.
  1266.          */
  1267.         if (return_both)
  1268.         {
  1269.         char *both = xmalloc(strlen(tag) + 2 + strlen(symtag));
  1270.         sprintf(both, "%s:%s", tag, symtag);
  1271.         if (tag_allocated)
  1272.             free (tag);
  1273.         return both;
  1274.         }
  1275.         else
  1276.         {
  1277.         if (! tag_allocated)
  1278.             tag = xstrdup (tag);
  1279.         return (tag);
  1280.         }
  1281.     }
  1282.     else
  1283.     {
  1284.         /* The revision wasn't there, so return the head or NULL */
  1285.         if (tag_allocated)
  1286.         free (tag);
  1287.         if (force_tag_match)
  1288.         return (NULL);
  1289.         else
  1290.         return (RCS_head (rcs));
  1291.     }
  1292.     }
  1293. }
  1294.  
  1295. /*
  1296.  * Return a "magic" revision as a virtual branch off of REV for the RCS file.
  1297.  * A "magic" revision is one which is unique in the RCS file.  By unique, I
  1298.  * mean we return a revision which:
  1299.  *    - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH)
  1300.  *    - has a revision component which is not an existing branch off REV
  1301.  *    - has a revision component which is not an existing magic revision
  1302.  *    - is an even-numbered revision, to avoid conflicts with vendor branches
  1303.  * The first point is what makes it "magic".
  1304.  *
  1305.  * As an example, if we pass in 1.37 as REV, we will look for an existing
  1306.  * branch called 1.37.2.  If it did not exist, we would look for an
  1307.  * existing symbolic tag with a numeric part equal to 1.37.0.2.  If that
  1308.  * didn't exist, then we know that the 1.37.2 branch can be reserved by
  1309.  * creating a symbolic tag with 1.37.0.2 as the numeric part.
  1310.  *
  1311.  * This allows us to fork development with very little overhead -- just a
  1312.  * symbolic tag is used in the RCS file.  When a commit is done, a physical
  1313.  * branch is dynamically created to hold the new revision.
  1314.  *
  1315.  * Note: We assume that REV is an RCS revision and not a branch number.
  1316.  */
  1317. static char *check_rev;
  1318. char *
  1319. RCS_magicrev (rcs, rev)
  1320.     RCSNode *rcs;
  1321.     char *rev;
  1322. {
  1323.     int rev_num;
  1324.     char *xrev, *test_branch;
  1325.  
  1326.     xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */
  1327.     check_rev = xrev;
  1328.  
  1329.     /* only look at even numbered branches */
  1330.     for (rev_num = 2; ; rev_num += 2)
  1331.     {
  1332.     /* see if the physical branch exists */
  1333.     (void) sprintf (xrev, "%s.%d", rev, rev_num);
  1334.     test_branch = RCS_getbranch (rcs, xrev, 1);
  1335.     if (test_branch != NULL)    /* it did, so keep looking */
  1336.     {
  1337.         free (test_branch);
  1338.         continue;
  1339.     }
  1340.  
  1341.     /* now, create a "magic" revision */
  1342.     (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num);
  1343.  
  1344.     /* walk the symbols list to see if a magic one already exists */
  1345.     if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0)
  1346.         continue;
  1347.  
  1348.     /* we found a free magic branch.  Claim it as ours */
  1349.     return (xrev);
  1350.     }
  1351. }
  1352.  
  1353. /*
  1354.  * walklist proc to look for a match in the symbols list.
  1355.  * Returns 0 if the symbol does not match, 1 if it does.
  1356.  */
  1357. static int
  1358. checkmagic_proc (p, closure)
  1359.     Node *p;
  1360.     void *closure;
  1361. {
  1362.     if (strcmp (check_rev, p->data) == 0)
  1363.     return (1);
  1364.     else
  1365.     return (0);
  1366. }
  1367.  
  1368. /*
  1369.  * Given an RCSNode, returns non-zero if the specified revision number 
  1370.  * or symbolic tag resolves to a "branch" within the rcs file.
  1371.  *
  1372.  * FIXME: this is the same as RCS_nodeisbranch except for the special 
  1373.  *        case for handling a null rcsnode.
  1374.  */
  1375. int
  1376. RCS_isbranch (rcs, rev)
  1377.     RCSNode *rcs;
  1378.     const char *rev;
  1379. {
  1380.     /* numeric revisions are easy -- even number of dots is a branch */
  1381.     if (isdigit (*rev))
  1382.     return ((numdots (rev) & 1) == 0);
  1383.  
  1384.     /* assume a revision if you can't find the RCS info */
  1385.     if (rcs == NULL)
  1386.     return (0);
  1387.  
  1388.     /* now, look for a match in the symbols list */
  1389.     return (RCS_nodeisbranch (rcs, rev));
  1390. }
  1391.  
  1392. /*
  1393.  * Given an RCSNode, returns non-zero if the specified revision number
  1394.  * or symbolic tag resolves to a "branch" within the rcs file.  We do
  1395.  * take into account any magic branches as well.
  1396.  */
  1397. int
  1398. RCS_nodeisbranch (rcs, rev)
  1399.     RCSNode *rcs;
  1400.     const char *rev;
  1401. {
  1402.     int dots;
  1403.     char *version;
  1404.  
  1405.     /* numeric revisions are easy -- even number of dots is a branch */
  1406.     if (isdigit (*rev))
  1407.     return ((numdots (rev) & 1) == 0);
  1408.  
  1409.     version = translate_symtag (rcs, rev);
  1410.     if (version == NULL)
  1411.     return (0);
  1412.     dots = numdots (version);
  1413.     if ((dots & 1) == 0)
  1414.     {
  1415.     free (version);
  1416.     return (1);
  1417.     }
  1418.  
  1419.     /* got a symbolic tag match, but it's not a branch; see if it's magic */
  1420.     if (dots > 2)
  1421.     {
  1422.     char *magic;
  1423.     char *branch = strrchr (version, '.');
  1424.     char *cp = branch - 1;
  1425.     while (*cp != '.')
  1426.         cp--;
  1427.  
  1428.     /* see if we have .magic-branch. (".0.") */
  1429.     magic = xmalloc (strlen (version) + 1);
  1430.     (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
  1431.     if (strncmp (magic, cp, strlen (magic)) == 0)
  1432.     {
  1433.         free (magic);
  1434.         free (version);
  1435.         return (1);
  1436.     }
  1437.     free (magic);
  1438.     free (version);
  1439.     }
  1440.     return (0);
  1441. }
  1442.  
  1443. /*
  1444.  * Returns a pointer to malloc'ed memory which contains the branch
  1445.  * for the specified *symbolic* tag.  Magic branches are handled correctly.
  1446.  */
  1447. char *
  1448. RCS_whatbranch (rcs, rev)
  1449.     RCSNode *rcs;
  1450.     const char *rev;
  1451. {
  1452.     char *version;
  1453.     int dots;
  1454.  
  1455.     /* assume no branch if you can't find the RCS info */
  1456.     if (rcs == NULL)
  1457.     return ((char *) NULL);
  1458.  
  1459.     /* now, look for a match in the symbols list */
  1460.     version = translate_symtag (rcs, rev);
  1461.     if (version == NULL)
  1462.     return ((char *) NULL);
  1463.     dots = numdots (version);
  1464.     if ((dots & 1) == 0)
  1465.     return (version);
  1466.  
  1467.     /* got a symbolic tag match, but it's not a branch; see if it's magic */
  1468.     if (dots > 2)
  1469.     {
  1470.     char *magic;
  1471.     char *branch = strrchr (version, '.');
  1472.     char *cp = branch++ - 1;
  1473.     while (*cp != '.')
  1474.         cp--;
  1475.  
  1476.     /* see if we have .magic-branch. (".0.") */
  1477.     magic = xmalloc (strlen (version) + 1);
  1478.     (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
  1479.     if (strncmp (magic, cp, strlen (magic)) == 0)
  1480.     {
  1481.         /* yep.  it's magic.  now, construct the real branch */
  1482.         *cp = '\0';            /* turn it into a revision */
  1483.         (void) sprintf (magic, "%s.%s", version, branch);
  1484.         free (version);
  1485.         return (magic);
  1486.     }
  1487.     free (magic);
  1488.     free (version);
  1489.     }
  1490.     return ((char *) NULL);
  1491. }
  1492.  
  1493. /*
  1494.  * Get the head of the specified branch.  If the branch does not exist,
  1495.  * return NULL or RCS_head depending on force_tag_match
  1496.  */
  1497. char *
  1498. RCS_getbranch (rcs, tag, force_tag_match)
  1499.     RCSNode *rcs;
  1500.     char *tag;
  1501.     int force_tag_match;
  1502. {
  1503.     Node *p, *head;
  1504.     RCSVers *vn;
  1505.     char *xtag;
  1506.     char *nextvers;
  1507.     char *cp;
  1508.  
  1509.     /* make sure we have something to look at... */
  1510.     assert (rcs != NULL);
  1511.  
  1512.     if (rcs->flags & PARTIAL)
  1513.     RCS_reparsercsfile (rcs, 0, NULL);
  1514.  
  1515.     /* find out if the tag contains a dot, or is on the trunk */
  1516.     cp = strrchr (tag, '.');
  1517.  
  1518.     /* trunk processing is the special case */
  1519.     if (cp == NULL)
  1520.     {
  1521.     xtag = xmalloc (strlen (tag) + 1 + 1);    /* +1 for an extra . */
  1522.     (void) strcpy (xtag, tag);
  1523.     (void) strcat (xtag, ".");
  1524.     for (cp = rcs->head; cp != NULL;)
  1525.     {
  1526.         if (strncmp (xtag, cp, strlen (xtag)) == 0)
  1527.         break;
  1528.         p = findnode (rcs->versions, cp);
  1529.         if (p == NULL)
  1530.         {
  1531.         free (xtag);
  1532.         if (force_tag_match)
  1533.             return (NULL);
  1534.         else
  1535.             return (RCS_head (rcs));
  1536.         }
  1537.         vn = (RCSVers *) p->data;
  1538.         cp = vn->next;
  1539.     }
  1540.     free (xtag);
  1541.     if (cp == NULL)
  1542.     {
  1543.         if (force_tag_match)
  1544.         return (NULL);
  1545.         else
  1546.         return (RCS_head (rcs));
  1547.     }
  1548.     return (xstrdup (cp));
  1549.     }
  1550.  
  1551.     /* if it had a `.', terminate the string so we have the base revision */
  1552.     *cp = '\0';
  1553.  
  1554.     /* look up the revision this branch is based on */
  1555.     p = findnode (rcs->versions, tag);
  1556.  
  1557.     /* put the . back so we have the branch again */
  1558.     *cp = '.';
  1559.  
  1560.     if (p == NULL)
  1561.     {
  1562.     /* if the base revision didn't exist, return head or NULL */
  1563.     if (force_tag_match)
  1564.         return (NULL);
  1565.     else
  1566.         return (RCS_head (rcs));
  1567.     }
  1568.  
  1569.     /* find the first element of the branch we are looking for */
  1570.     vn = (RCSVers *) p->data;
  1571.     if (vn->branches == NULL)
  1572.     return (NULL);
  1573.     xtag = xmalloc (strlen (tag) + 1 + 1);    /* 1 for the extra '.' */
  1574.     (void) strcpy (xtag, tag);
  1575.     (void) strcat (xtag, ".");
  1576.     head = vn->branches->list;
  1577.     for (p = head->next; p != head; p = p->next)
  1578.     if (strncmp (p->key, xtag, strlen (xtag)) == 0)
  1579.         break;
  1580.     free (xtag);
  1581.  
  1582.     if (p == head)
  1583.     {
  1584.     /* we didn't find a match so return head or NULL */
  1585.     if (force_tag_match)
  1586.         return (NULL);
  1587.     else
  1588.         return (RCS_head (rcs));
  1589.     }
  1590.  
  1591.     /* now walk the next pointers of the branch */
  1592.     nextvers = p->key;
  1593.     do
  1594.     {
  1595.     p = findnode (rcs->versions, nextvers);
  1596.     if (p == NULL)
  1597.     {
  1598.         /* a link in the chain is missing - return head or NULL */
  1599.         if (force_tag_match)
  1600.         return (NULL);
  1601.         else
  1602.         return (RCS_head (rcs));
  1603.     }
  1604.     vn = (RCSVers *) p->data;
  1605.     nextvers = vn->next;
  1606.     } while (nextvers != NULL);
  1607.  
  1608.     /* we have the version in our hand, so go for it */
  1609.     return (xstrdup (vn->version));
  1610. }
  1611.  
  1612. /*
  1613.  * Get the head of the RCS file.  If branch is set, this is the head of the
  1614.  * branch, otherwise the real head
  1615.  */
  1616. char *
  1617. RCS_head (rcs)
  1618.     RCSNode *rcs;
  1619. {
  1620.     /* make sure we have something to look at... */
  1621.     assert (rcs != NULL);
  1622.  
  1623.     /*
  1624.      * NOTE: we call getbranch with force_tag_match set to avoid any
  1625.      * possibility of recursion
  1626.      */
  1627.     if (rcs->branch)
  1628.     return (RCS_getbranch (rcs, rcs->branch, 1));
  1629.     else
  1630.     return (xstrdup (rcs->head));
  1631. }
  1632.  
  1633. /*
  1634.  * Get the most recent revision, based on the supplied date, but use some
  1635.  * funky stuff and follow the vendor branch maybe
  1636.  */
  1637. char *
  1638. RCS_getdate (rcs, date, force_tag_match)
  1639.     RCSNode *rcs;
  1640.     char *date;
  1641.     int force_tag_match;
  1642. {
  1643.     char *cur_rev = NULL;
  1644.     char *retval = NULL;
  1645.     Node *p;
  1646.     RCSVers *vers = NULL;
  1647.  
  1648.     /* make sure we have something to look at... */
  1649.     assert (rcs != NULL);
  1650.  
  1651.     if (rcs->flags & PARTIAL)
  1652.     RCS_reparsercsfile (rcs, 0, NULL);
  1653.  
  1654.     /* if the head is on a branch, try the branch first */
  1655.     if (rcs->branch != NULL)
  1656.     retval = RCS_getdatebranch (rcs, date, rcs->branch);
  1657.  
  1658.     /* if we found a match, we are done */
  1659.     if (retval != NULL)
  1660.     return (retval);
  1661.  
  1662.     /* otherwise if we have a trunk, try it */
  1663.     if (rcs->head)
  1664.     {
  1665.     p = findnode (rcs->versions, rcs->head);
  1666.     while (p != NULL)
  1667.     {
  1668.         /* if the date of this one is before date, take it */
  1669.         vers = (RCSVers *) p->data;
  1670.         if (RCS_datecmp (vers->date, date) <= 0)
  1671.         {
  1672.         cur_rev = vers->version;
  1673.         break;
  1674.         }
  1675.  
  1676.         /* if there is a next version, find the node */
  1677.         if (vers->next != NULL)
  1678.         p = findnode (rcs->versions, vers->next);
  1679.         else
  1680.         p = (Node *) NULL;
  1681.     }
  1682.     }
  1683.  
  1684.     /*
  1685.      * at this point, either we have the revision we want, or we have the
  1686.      * first revision on the trunk (1.1?) in our hands
  1687.      */
  1688.  
  1689.     /* if we found what we're looking for, and it's not 1.1 return it */
  1690.     if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0)
  1691.     return (xstrdup (cur_rev));
  1692.  
  1693.     /* look on the vendor branch */
  1694.     retval = RCS_getdatebranch (rcs, date, CVSBRANCH);
  1695.  
  1696.     /*
  1697.      * if we found a match, return it; otherwise, we return the first
  1698.      * revision on the trunk or NULL depending on force_tag_match and the
  1699.      * date of the first rev
  1700.      */
  1701.     if (retval != NULL)
  1702.     return (retval);
  1703.  
  1704.     if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0)
  1705.     return (xstrdup (vers->version));
  1706.     else
  1707.     return (NULL);
  1708. }
  1709.  
  1710. /*
  1711.  * Look up the last element on a branch that was put in before the specified
  1712.  * date (return the rev or NULL)
  1713.  */
  1714. static char *
  1715. RCS_getdatebranch (rcs, date, branch)
  1716.     RCSNode *rcs;
  1717.     char *date;
  1718.     char *branch;
  1719. {
  1720.     char *cur_rev = NULL;
  1721.     char *cp;
  1722.     char *xbranch, *xrev;
  1723.     Node *p;
  1724.     RCSVers *vers;
  1725.  
  1726.     /* look up the first revision on the branch */
  1727.     xrev = xstrdup (branch);
  1728.     cp = strrchr (xrev, '.');
  1729.     if (cp == NULL)
  1730.     {
  1731.     free (xrev);
  1732.     return (NULL);
  1733.     }
  1734.     *cp = '\0';                /* turn it into a revision */
  1735.  
  1736.     assert (rcs != NULL);
  1737.  
  1738.     if (rcs->flags & PARTIAL)
  1739.     RCS_reparsercsfile (rcs, 0, NULL);
  1740.  
  1741.     p = findnode (rcs->versions, xrev);
  1742.     free (xrev);
  1743.     if (p == NULL)
  1744.     return (NULL);
  1745.     vers = (RCSVers *) p->data;
  1746.  
  1747.     /* Tentatively use this revision, if it is early enough.  */
  1748.     if (RCS_datecmp (vers->date, date) <= 0)
  1749.     cur_rev = vers->version;
  1750.  
  1751.     /* if no branches list, return now */
  1752.     if (vers->branches == NULL)
  1753.     return xstrdup (cur_rev);
  1754.  
  1755.     /* walk the branches list looking for the branch number */
  1756.     xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */
  1757.     (void) strcpy (xbranch, branch);
  1758.     (void) strcat (xbranch, ".");
  1759.     for (p = vers->branches->list->next; p != vers->branches->list; p = p->next)
  1760.     if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
  1761.         break;
  1762.     free (xbranch);
  1763.     if (p == vers->branches->list)
  1764.     {
  1765.     /* FIXME: This case would seem to imply that the RCS file is
  1766.            somehow invalid.  Should we give an error message?  */
  1767.     return (NULL);
  1768.     }
  1769.  
  1770.     p = findnode (rcs->versions, p->key);
  1771.  
  1772.     /* walk the next pointers until you find the end, or the date is too late */
  1773.     while (p != NULL)
  1774.     {
  1775.     vers = (RCSVers *) p->data;
  1776.     if (RCS_datecmp (vers->date, date) <= 0)
  1777.         cur_rev = vers->version;
  1778.     else
  1779.         break;
  1780.  
  1781.     /* if there is a next version, find the node */
  1782.     if (vers->next != NULL)
  1783.         p = findnode (rcs->versions, vers->next);
  1784.     else
  1785.         p = (Node *) NULL;
  1786.     }
  1787.  
  1788.     /* Return whatever we found, which may be NULL.  */
  1789.     return xstrdup (cur_rev);
  1790. }
  1791.  
  1792. /*
  1793.  * Compare two dates in RCS format. Beware the change in format on January 1,
  1794.  * 2000, when years go from 2-digit to full format.
  1795.  */
  1796. int
  1797. RCS_datecmp (date1, date2)
  1798.     char *date1, *date2;
  1799. {
  1800.     int length_diff = strlen (date1) - strlen (date2);
  1801.  
  1802.     return (length_diff ? length_diff : strcmp (date1, date2));
  1803. }
  1804.  
  1805. /*
  1806.  * Lookup the specified revision in the ,v file and return, in the date
  1807.  * argument, the date specified for the revision *minus one second*, so that
  1808.  * the logically previous revision will be found later.
  1809.  * 
  1810.  * Returns zero on failure, RCS revision time as a Unix "time_t" on success.
  1811.  */
  1812. time_t
  1813. RCS_getrevtime (rcs, rev, date, fudge)
  1814.     RCSNode *rcs;
  1815.     char *rev;
  1816.     char *date;
  1817.     int fudge;
  1818. {
  1819.     char tdate[MAXDATELEN];
  1820.     struct tm xtm, *ftm;
  1821.     time_t revdate = 0;
  1822.     Node *p;
  1823.     RCSVers *vers;
  1824.  
  1825.     /* make sure we have something to look at... */
  1826.     assert (rcs != NULL);
  1827.  
  1828.     if (rcs->flags & PARTIAL)
  1829.     RCS_reparsercsfile (rcs, 0, NULL);
  1830.  
  1831.     /* look up the revision */
  1832.     p = findnode (rcs->versions, rev);
  1833.     if (p == NULL)
  1834.     return (-1);
  1835.     vers = (RCSVers *) p->data;
  1836.  
  1837.     /* split up the date */
  1838.     ftm = &xtm;
  1839.     (void) sscanf (vers->date, SDATEFORM, &ftm->tm_year, &ftm->tm_mon,
  1840.            &ftm->tm_mday, &ftm->tm_hour, &ftm->tm_min,
  1841.            &ftm->tm_sec);
  1842.  
  1843.     /* If the year is from 1900 to 1999, RCS files contain only two
  1844.        digits, and sscanf gives us a year from 0-99.  If the year is
  1845.        2000+, RCS files contain all four digits and we subtract 1900,
  1846.        because the tm_year field should contain years since 1900.  */
  1847.  
  1848.     if (ftm->tm_year > 1900)
  1849.     ftm->tm_year -= 1900;
  1850.  
  1851.     /* put the date in a form getdate can grok */
  1852. #ifdef HAVE_RCS5
  1853.     (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon,
  1854.             ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
  1855.             ftm->tm_min, ftm->tm_sec);
  1856. #else
  1857.     (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon,
  1858.             ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
  1859.             ftm->tm_min, ftm->tm_sec);
  1860. #endif
  1861.  
  1862.     /* turn it into seconds since the epoch */
  1863.     revdate = get_date (tdate, (struct timeb *) NULL);
  1864.     if (revdate != (time_t) -1)
  1865.     {
  1866.     revdate -= fudge;        /* remove "fudge" seconds */
  1867.     if (date)
  1868.     {
  1869.         /* put an appropriate string into ``date'' if we were given one */
  1870. #ifdef HAVE_RCS5
  1871.         ftm = gmtime (&revdate);
  1872. #else
  1873.         ftm = localtime (&revdate);
  1874. #endif
  1875.         (void) sprintf (date, DATEFORM,
  1876.                 ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900),
  1877.                 ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour,
  1878.                 ftm->tm_min, ftm->tm_sec);
  1879.     }
  1880.     }
  1881.     return (revdate);
  1882. }
  1883.  
  1884. List *
  1885. RCS_symbols(rcs)
  1886.     RCSNode *rcs;
  1887. {
  1888.     assert(rcs != NULL);
  1889.  
  1890.     if (rcs->flags & PARTIAL)
  1891.     RCS_reparsercsfile (rcs, 0, NULL);
  1892.  
  1893.     if (rcs->symbols_data) {
  1894.     rcs->symbols = getlist ();
  1895.     do_symbols (rcs->symbols, rcs->symbols_data);
  1896.     free(rcs->symbols_data);
  1897.     rcs->symbols_data = NULL;
  1898.     }
  1899.  
  1900.     return rcs->symbols;
  1901. }
  1902.  
  1903. /*
  1904.  * Return the version associated with a particular symbolic tag.
  1905.  */
  1906. static char *
  1907. translate_symtag (rcs, tag)
  1908.     RCSNode *rcs;
  1909.     const char *tag;
  1910. {
  1911.     if (rcs->flags & PARTIAL)
  1912.     RCS_reparsercsfile (rcs, 0, NULL);
  1913.  
  1914.     if (rcs->symbols != NULL)
  1915.     {
  1916.     Node *p;
  1917.  
  1918.     /* The symbols have already been converted into a list.  */
  1919.     p = findnode (rcs->symbols, tag);
  1920.     if (p == NULL)
  1921.         return NULL;
  1922.  
  1923.     return xstrdup (p->data);
  1924.     }
  1925.  
  1926.     if (rcs->symbols_data != NULL)
  1927.     {
  1928.     size_t len;
  1929.     char *cp;
  1930.  
  1931.     /* Look through the RCS symbols information.  This is like
  1932.            do_symbols, but we don't add the information to a list.  In
  1933.            most cases, we will only be called once for this file, so
  1934.            generating the list is unnecessary overhead.  */
  1935.  
  1936.     len = strlen (tag);
  1937.     cp = rcs->symbols_data;
  1938.     while ((cp = strchr (cp, tag[0])) != NULL)
  1939.     {
  1940.         if ((cp == rcs->symbols_data || whitespace (cp[-1]))
  1941.         && strncmp (cp, tag, len) == 0
  1942.         && cp[len] == ':')
  1943.         {
  1944.         char *v, *r;
  1945.  
  1946.         /* We found the tag.  Return the version number.  */
  1947.  
  1948.         cp += len + 1;
  1949.         v = cp;
  1950.         while (! whitespace (*cp) && *cp != '\0')
  1951.             ++cp;
  1952.         r = xmalloc (cp - v + 1);
  1953.         strncpy (r, v, cp - v);
  1954.         r[cp - v] = '\0';
  1955.         return r;
  1956.         }
  1957.  
  1958.         while (! whitespace (*cp) && *cp != '\0')
  1959.         ++cp;
  1960.     }
  1961.     }
  1962.  
  1963.     return NULL;
  1964. }
  1965.  
  1966. /*
  1967.  * The argument ARG is the getopt remainder of the -k option specified on the
  1968.  * command line.  This function returns malloc'ed space that can be used
  1969.  * directly in calls to RCS V5, with the -k flag munged correctly.
  1970.  */
  1971. char *
  1972. RCS_check_kflag (arg)
  1973.     const char *arg;
  1974. {
  1975.     static const char *const kflags[] =
  1976.     {"kv", "kvl", "k", "v", "o", "b", (char *) NULL};
  1977.     static const char *const  keyword_usage[] =
  1978.     {
  1979.       "%s %s: invalid RCS keyword expansion mode\n",
  1980.       "Valid expansion modes include:\n",
  1981.       "   -kkv\tGenerate keywords using the default form.\n",
  1982.       "   -kkvl\tLike -kkv, except locker's name inserted.\n",
  1983.       "   -kk\tGenerate only keyword names in keyword strings.\n",
  1984.       "   -kv\tGenerate only keyword values in keyword strings.\n",
  1985.       "   -ko\tGenerate the old keyword string (no changes from checked in file).\n",
  1986.       "   -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n",
  1987.       NULL,
  1988.     };
  1989.     char karg[10];
  1990.     char const *const *cpp = NULL;
  1991.  
  1992. #ifndef HAVE_RCS5
  1993.     error (1, 0, "%s %s: your version of RCS does not support the -k option",
  1994.        program_name, command_name);
  1995. #endif
  1996.  
  1997.     if (arg)
  1998.     {
  1999.     for (cpp = kflags; *cpp != NULL; cpp++)
  2000.     {
  2001.         if (strcmp (arg, *cpp) == 0)
  2002.         break;
  2003.     }
  2004.     }
  2005.  
  2006.     if (arg == NULL || *cpp == NULL)
  2007.     {
  2008.     usage (keyword_usage);
  2009.     }
  2010.  
  2011.     (void) sprintf (karg, "-k%s", *cpp);
  2012.     return (xstrdup (karg));
  2013. }
  2014.  
  2015. /*
  2016.  * Do some consistency checks on the symbolic tag... These should equate
  2017.  * pretty close to what RCS checks, though I don't know for certain.
  2018.  */
  2019. void
  2020. RCS_check_tag (tag)
  2021.     const char *tag;
  2022. {
  2023.     char *invalid = "$,.:;@";        /* invalid RCS tag characters */
  2024.     const char *cp;
  2025.  
  2026.     /*
  2027.      * The first character must be an alphabetic letter. The remaining
  2028.      * characters cannot be non-visible graphic characters, and must not be
  2029.      * in the set of "invalid" RCS identifier characters.
  2030.      */
  2031.     if (isalpha (*tag))
  2032.     {
  2033.     for (cp = tag; *cp; cp++)
  2034.     {
  2035.         if (!isgraph (*cp))
  2036.         error (1, 0, "tag `%s' has non-visible graphic characters",
  2037.                tag);
  2038.         if (strchr (invalid, *cp))
  2039.         error (1, 0, "tag `%s' must not contain the characters `%s'",
  2040.                tag, invalid);
  2041.     }
  2042.     }
  2043.     else
  2044.     error (1, 0, "tag `%s' must start with a letter", tag);
  2045. }
  2046.  
  2047. /*
  2048.  * Return true if RCS revision with TAG is a dead revision.
  2049.  */
  2050. int
  2051. RCS_isdead (rcs, tag)
  2052.     RCSNode *rcs;
  2053.     const char *tag;
  2054. {
  2055.     Node *p;
  2056.     RCSVers *version;
  2057.  
  2058.     if (rcs->flags & PARTIAL)
  2059.     RCS_reparsercsfile (rcs, 0, NULL);
  2060.  
  2061.     p = findnode (rcs->versions, tag);
  2062.     if (p == NULL)
  2063.     return (0);
  2064.  
  2065.     version = (RCSVers *) p->data;
  2066.     return (version->dead);
  2067. }
  2068.  
  2069. /* Return the RCS keyword expansion mode.  For example "b" for binary.
  2070.    Returns a pointer into storage which is allocated and freed along with
  2071.    the rest of the RCS information; the caller should not modify this
  2072.    storage.  Returns NULL if the RCS file does not specify a keyword
  2073.    expansion mode; for all other errors, die with a fatal error.  */
  2074. char *
  2075. RCS_getexpand (rcs)
  2076.     RCSNode *rcs;
  2077. {
  2078.     assert (rcs != NULL);
  2079.     if (rcs->flags & PARTIAL)
  2080.     RCS_reparsercsfile (rcs, 0, NULL);
  2081.     return rcs->expand;
  2082. }
  2083.  
  2084. /* Check out a revision from RCS.  This function optimizes by reading
  2085.    the head version directly if it is easy.  Check out the revision
  2086.    into WORKFILE, or to standard output if WORKFILE is NULL.  If
  2087.    WORKFILE is "", let RCS pick the working file name.  TAG is the tag
  2088.    to check out, or NULL if one should check out the head of the
  2089.    default branch.  OPTIONS is a string such as -kb or -kkv, for
  2090.    keyword expansion options, or NULL if there are none.  If WORKFILE
  2091.    is NULL, run regardless of noexec; if non-NULL, noexec inhibits
  2092.    execution.  SOUT is what to do with standard output (typically
  2093.    RUN_TTY).  If FLAGS & RCS_FLAGS_FORCE, check out even on top of an
  2094.    existing file.  */
  2095.  
  2096. int
  2097. RCS_fast_checkout (rcs, workfile, tag, options, sout, flags)
  2098.      RCSNode *rcs;
  2099.      char *workfile;
  2100.      char *tag;
  2101.      char *options;
  2102.      char *sout;
  2103.      int flags;
  2104. {
  2105.     if ((workfile == NULL || *workfile != '\0')
  2106.     && ! noexec
  2107.     && (sout == RUN_TTY || workfile == NULL))
  2108.     {
  2109.         FILE *fp;
  2110.     struct stat sb;
  2111.         int found;
  2112.         char *key;
  2113.     char *value;
  2114.     size_t len;
  2115.     char *ouroptions;
  2116.  
  2117.     if (tag == NULL || strcmp (tag, rcs->head) == 0)
  2118.     {
  2119.         /* We want the head revision.  Try to read it directly.  */
  2120.  
  2121.         if (rcs->flags & PARTIAL)
  2122.         RCS_reparsercsfile (rcs, 0, &fp);
  2123.         else
  2124.         {
  2125.         fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
  2126.         if (fp == NULL)
  2127.             error (1, 0, "unable to reopen `%s'", rcs->path);
  2128.         if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
  2129.             error (1, 0, "cannot fseek RCS file");
  2130.         }
  2131.  
  2132.         found = 0;
  2133.         getrcsrev (fp, &key);
  2134.         while (getrcskey (fp, &key, &value, &len) >= 0)
  2135.         {
  2136.         if (strcmp (key, "text") == 0)
  2137.         {
  2138.             found = 1;
  2139.             break;
  2140.         }
  2141.         }
  2142.  
  2143.         if (fstat (fileno (fp), &sb) < 0)
  2144.         error (1, errno, "cannot fstat %s", rcs->path);
  2145.  
  2146.         if (fclose (fp) < 0)
  2147.         error (0, errno, "cannot close %s", rcs->path);
  2148.     }
  2149.     else
  2150.     {
  2151.         /* It isn't the head revision of the trunk.  We'll need to
  2152.            walk through the deltas.  */
  2153.         /* Numeric version of TAG.  (Should we be having the caller
  2154.            pass us this in addition to TAG which is often symbolic for the
  2155.            Name keyword?)  */
  2156.         char *num;
  2157.  
  2158.         fp = NULL;
  2159.         if (rcs->flags & PARTIAL)
  2160.         RCS_reparsercsfile (rcs, 0, &fp);
  2161.         num = RCS_getversion (rcs, tag, NULL, 1, 0);
  2162.         if (num == NULL)
  2163.         {
  2164.         error (0, 0, "internal warning: cannot find revision");
  2165.         found = 0;
  2166.         }
  2167.         else
  2168.         {
  2169.         if (fp == NULL)
  2170.         {
  2171.             /* If RCS_deltas didn't close the file, we could
  2172.                use fstat here too.  Probably should change it
  2173.                thusly....  */
  2174.             if (stat (rcs->path, &sb) < 0)
  2175.             error (1, errno, "cannot stat %s", rcs->path);
  2176.         }
  2177.         else
  2178.         {
  2179.             if (fstat (fileno (fp), &sb) < 0)
  2180.             error (1, errno, "cannot fstat %s", rcs->path);
  2181.         }
  2182.  
  2183.         RCS_deltas (rcs, fp, num, RCS_FETCH, &value, &len);
  2184.         found = 1;
  2185.         free (num);
  2186.         }
  2187.     }
  2188.  
  2189.     /* I'm not completely sure that checking rcs->expand is necessary
  2190.        or even desirable.  It would appear that Version_TS takes care
  2191.        of that.  */
  2192.     ouroptions = (options != NULL && strlen (options) > 2
  2193.               ? options + 2
  2194.               : (rcs->expand != NULL ? rcs->expand : ""));
  2195.     if (found)
  2196.     {
  2197.         if (strcmp (ouroptions, "o") != 0
  2198.             && strcmp (ouroptions, "b") != 0)
  2199.         {
  2200.             register int inkeyword;
  2201.             register char *s, *send;
  2202.  
  2203.             /* Keyword expansion is being done.  Make sure the
  2204.                    text does not contain any keywords.  If it does
  2205.                    have any, do the regular checkout.  */
  2206.         inkeyword = 0;
  2207.         send = value + len;
  2208.         for (s = value; s < send; s++)
  2209.         {
  2210.             register char c;
  2211.  
  2212.             c = *s;
  2213.             if (c == '$')
  2214.             {
  2215.                 if (inkeyword)
  2216.             {
  2217.                 found = 0;
  2218.                 break;
  2219.             }
  2220.             inkeyword = 1;
  2221.             }
  2222.             else if (c == ':')
  2223.             {
  2224.                 if (inkeyword)
  2225.             {
  2226.                 found = 0;
  2227.                 break;
  2228.             }
  2229.             }
  2230.             else if (inkeyword && ! isalpha ((unsigned char) c))
  2231.                 inkeyword = 0;
  2232.         }
  2233.         }
  2234.     }
  2235.  
  2236.     if (found)
  2237.     {
  2238.         FILE *ofp;
  2239.  
  2240.         /* We have the text we want.  */
  2241.  
  2242.         if (workfile == NULL)
  2243.         {
  2244.             if (sout == RUN_TTY)
  2245.             ofp = stdout;
  2246.         else
  2247.         {
  2248.             ofp = CVS_FOPEN (sout, strcmp (ouroptions, "b") == 0 ? "wb" : "w");
  2249.             if (ofp == NULL)
  2250.                 error (1, errno, "cannot open %s", sout);
  2251.         }
  2252.         }
  2253.         else
  2254.         {
  2255.             ofp = CVS_FOPEN (workfile, strcmp (ouroptions, "b") == 0 ? "wb" : "w");
  2256.         if (ofp == NULL)
  2257.             error (1, errno, "cannot open %s", workfile);
  2258.         }
  2259.  
  2260.         if (fwrite (value, 1, len, ofp) != len)
  2261.         error (1, errno, "cannot write %s",
  2262.                workfile == NULL ? "stdout" : workfile);
  2263.  
  2264.         if (workfile != NULL)
  2265.         {
  2266.             if (fclose (ofp) < 0)
  2267.             error (1, errno, "cannot close %s", workfile);
  2268.             if (chmod (workfile,
  2269.                sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) < 0)
  2270.             error (0, errno, "cannot change mode of file %s",
  2271.                workfile);
  2272.         }
  2273.         else if (sout != RUN_TTY)
  2274.         {
  2275.         if (fclose (ofp) < 0)
  2276.             error (1, errno, "cannot close %s", sout);
  2277.         }
  2278.  
  2279.         return 0;
  2280.     }
  2281.     }
  2282.  
  2283.     /* We were not able to optimize retrieving this revision.  */
  2284.  
  2285.     return RCS_checkout (rcs->path, workfile, tag, options, sout, flags);
  2286. }
  2287.  
  2288. /* RCS_deltas and friends.  Processing of the deltas in RCS files.  */
  2289.  
  2290. /* Linked list of allocated blocks.  Seems kind of silly to
  2291.    reinvent the obstack wheel, and this isn't as nice as obstacks
  2292.    in some ways, but obstacks are pretty baroque.  */
  2293. struct allocblock
  2294. {
  2295.     char *text;
  2296.     struct allocblock *next;
  2297. };
  2298. struct allocblock *blocks;
  2299.  
  2300. static void *block_alloc PROTO ((size_t));
  2301.  
  2302. static void *
  2303. block_alloc (n)
  2304.     size_t n;
  2305. {
  2306.     struct allocblock *blk;
  2307.     blk = (struct allocblock *) xmalloc (sizeof (struct allocblock));
  2308.     blk->text = xmalloc (n);
  2309.     blk->next = blocks;
  2310.     blocks = blk;
  2311.     return blk->text;
  2312. }
  2313.  
  2314. static void block_free PROTO ((void));
  2315.  
  2316. static void
  2317. block_free ()
  2318. {
  2319.     struct allocblock *p;
  2320.     struct allocblock *q;
  2321.  
  2322.     p = blocks;
  2323.     while (p != NULL)
  2324.     {
  2325.     free (p->text);
  2326.     q = p->next;
  2327.     free (p);
  2328.     p = q;
  2329.     }
  2330.     blocks = NULL;
  2331. }
  2332.  
  2333. struct line
  2334. {
  2335.     /* Text of this line.  */
  2336.     char *text;
  2337.     /* Length of this line, not counting \n if has_newline is true.  */
  2338.     size_t len;
  2339.     /* Version in which it was introduced.  */
  2340.     RCSVers *vers;
  2341.     /* Nonzero if this line ends with \n.  This will always be true
  2342.        except possibly for the last line.  */
  2343.     int has_newline;
  2344. };
  2345.  
  2346. struct linevector
  2347. {
  2348.     /* How many lines in use for this linevector?  */
  2349.     unsigned int nlines;
  2350.     /* How many lines allocated for this linevector?  */
  2351.     unsigned int lines_alloced;
  2352.     /* Pointer to array containing a pointer to each line.  */
  2353.     struct line **vector;
  2354. };
  2355.  
  2356. static void linevector_init PROTO ((struct linevector *));
  2357.  
  2358. /* Initialize *VEC to be a linevector with no lines.  */
  2359. static void
  2360. linevector_init (vec)
  2361.     struct linevector *vec;
  2362. {
  2363.     vec->lines_alloced = 0;
  2364.     vec->nlines = 0;
  2365.     vec->vector = NULL;
  2366. }
  2367.  
  2368. static void linevector_add PROTO ((struct linevector *vec, char *text,
  2369.                    size_t len, RCSVers *vers,
  2370.                    unsigned int pos));
  2371.  
  2372. /* Given some text TEXT, add each of its lines to VEC before line POS
  2373.    (where line 0 is the first line).  The last line in TEXT may or may
  2374.    not be \n terminated.  All \n in TEXT are changed to \0 (FIXME: I
  2375.    don't think this is needed, or used, now that we have the ->len
  2376.    field).  Set the version for each of the new lines to VERS.  */
  2377. static void
  2378. linevector_add (vec, text, len, vers, pos)
  2379.     struct linevector *vec;
  2380.     char *text;
  2381.     size_t len;
  2382.     RCSVers *vers;
  2383.     unsigned int pos;
  2384. {
  2385.     char *textend;
  2386.     unsigned int i;
  2387.     unsigned int nnew;
  2388.     char *p;
  2389.     struct line *lines;
  2390.  
  2391.     if (len == 0)
  2392.     return;
  2393.  
  2394.     textend = text + len;
  2395.  
  2396.     /* Count the number of lines we will need to add.  */
  2397.     nnew = 1;
  2398.     for (p = text; p < textend; ++p)
  2399.     if (*p == '\n' && p + 1 < textend)
  2400.         ++nnew;
  2401.     /* Allocate the struct line's.  */
  2402.     lines = block_alloc (nnew * sizeof (struct line));
  2403.  
  2404.     /* Expand VEC->VECTOR if needed.  */
  2405.     if (vec->nlines + nnew >= vec->lines_alloced)
  2406.     {
  2407.     if (vec->lines_alloced == 0)
  2408.         vec->lines_alloced = 10;
  2409.     while (vec->nlines + nnew >= vec->lines_alloced)
  2410.         vec->lines_alloced *= 2;
  2411.     vec->vector = xrealloc (vec->vector,
  2412.                 vec->lines_alloced * sizeof (*vec->vector));
  2413.     }
  2414.  
  2415.     /* Make room for the new lines in VEC->VECTOR.  */
  2416.     for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i)
  2417.     vec->vector[i] = vec->vector[i - nnew];
  2418.  
  2419.     if (pos > vec->nlines)
  2420.     error (1, 0, "invalid rcs file: line to add out of range");
  2421.  
  2422.     /* Actually add the lines, to LINES and VEC->VECTOR.  */
  2423.     i = pos;
  2424.     lines[0].text = text;
  2425.     lines[0].vers = vers;
  2426.     lines[0].has_newline = 0;
  2427.     vec->vector[i++] = &lines[0];
  2428.     for (p = text; p < textend; ++p)
  2429.     if (*p == '\n')
  2430.     {
  2431.         *p = '\0';
  2432.         lines[i - pos - 1].has_newline = 1;
  2433.         if (p + 1 == textend)
  2434.         /* If there are no characters beyond the last newline, we
  2435.            don't consider it another line.  */
  2436.         break;
  2437.         lines[i - pos - 1].len = p - lines[i - pos - 1].text;
  2438.         lines[i - pos].text = p + 1;
  2439.         lines[i - pos].vers = vers;
  2440.         lines[i - pos].has_newline = 0;
  2441.         vec->vector[i] = &lines[i - pos];
  2442.         ++i;
  2443.     }
  2444.     lines[i - pos - 1].len = p - lines[i - pos - 1].text;
  2445.     vec->nlines += nnew;
  2446. }
  2447.  
  2448. static void linevector_delete PROTO ((struct linevector *, unsigned int,
  2449.                       unsigned int));
  2450.  
  2451. /* Remove NLINES lines from VEC at position POS (where line 0 is the
  2452.    first line).  */
  2453. static void
  2454. linevector_delete (vec, pos, nlines)
  2455.     struct linevector *vec;
  2456.     unsigned int pos;
  2457.     unsigned int nlines;
  2458. {
  2459.     unsigned int i;
  2460.     unsigned int last;
  2461.  
  2462.     last = vec->nlines - nlines;
  2463.     for (i = pos; i < last; ++i)
  2464.     vec->vector[i] = vec->vector[i + nlines];
  2465.     vec->nlines -= nlines;
  2466. }
  2467.  
  2468. static void linevector_copy PROTO ((struct linevector *, struct linevector *));
  2469.  
  2470. /* Copy FROM to TO, copying the vectors but not the lines pointed to.  */
  2471. static void
  2472. linevector_copy (to, from)
  2473.     struct linevector *to;
  2474.     struct linevector *from;
  2475. {
  2476.     if (from->nlines > to->lines_alloced)
  2477.     {
  2478.     if (to->lines_alloced == 0)
  2479.         to->lines_alloced = 10;
  2480.     while (from->nlines > to->lines_alloced)
  2481.         to->lines_alloced *= 2;
  2482.     to->vector = (struct line **)
  2483.         xrealloc (to->vector, to->lines_alloced * sizeof (*to->vector));
  2484.     }
  2485.     memcpy (to->vector, from->vector,
  2486.         from->nlines * sizeof (*to->vector));
  2487.     to->nlines = from->nlines;
  2488. }
  2489.  
  2490. static void linevector_free PROTO ((struct linevector *));
  2491.  
  2492. /* Free storage associated with linevector (that is, the vector but
  2493.    not the lines pointed to).  */
  2494. static void
  2495. linevector_free (vec)
  2496.     struct linevector *vec;
  2497. {
  2498.     if (vec->vector != NULL)
  2499.     free (vec->vector);
  2500. }
  2501.  
  2502. static char *month_printname PROTO ((char *));
  2503.  
  2504. /* Given a textual string giving the month (1-12), terminated with any
  2505.    character not recognized by atoi, return the 3 character name to
  2506.    print it with.  I do not think it is a good idea to change these
  2507.    strings based on the locale; they are standard abbreviations (for
  2508.    example in rfc822 mail messages) which should be widely understood.
  2509.    Returns a pointer into static readonly storage.  */
  2510. static char *
  2511. month_printname (month)
  2512.     char *month;
  2513. {
  2514.     static const char *const months[] =
  2515.       {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
  2516.      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  2517.     int mnum;
  2518.  
  2519.     mnum = atoi (month);
  2520.     if (mnum < 1 || mnum > 12)
  2521.     return "???";
  2522.     return (char *)months[mnum - 1];
  2523. }
  2524.  
  2525. /* Walk the deltas in RCS to get to revision VERSION.
  2526.  
  2527.    If OP is RCS_ANNOTATE, then write annotations using cvs_output.
  2528.  
  2529.    If OP is RCS_FETCH, then put the contents of VERSION into a
  2530.    newly-malloc'd array and put a pointer to it in *TEXT.  Each line
  2531.    is \n terminated; the caller is responsible for converting text
  2532.    files if desired.  The total length is put in *LEN.
  2533.  
  2534.    If FP is non-NULL, it should be a file descriptor open to the file
  2535.    RCS with file position pointing to the deltas.  We close the file
  2536.    when we are done.
  2537.  
  2538.    On error, give a fatal error.  */
  2539.  
  2540. static void
  2541. RCS_deltas (rcs, fp, version, op, text, len)
  2542.     RCSNode *rcs;
  2543.     FILE *fp;
  2544.     char *version;
  2545.     enum rcs_delta_op op;
  2546.     char **text;
  2547.     size_t *len;
  2548. {
  2549.     char *branchversion;
  2550.     char *cpversion;
  2551.     char *key;
  2552.     char *value;
  2553.     size_t vallen;
  2554.     RCSVers *vers;
  2555.     RCSVers *prev_vers;
  2556.     RCSVers *trunk_vers;
  2557.     char *next;
  2558.     int n;
  2559.     int ishead, isnext, isversion, onbranch;
  2560.     Node *node;
  2561.     struct linevector headlines;
  2562.     struct linevector curlines;
  2563.     struct linevector trunklines;
  2564.     int foundhead;
  2565.  
  2566.     if (fp == NULL)
  2567.     {
  2568.     fp = CVS_FOPEN (rcs->path, FOPEN_BINARY_READ);
  2569.     if (fp == NULL)
  2570.         error (1, 0, "unable to reopen `%s'", rcs->path);
  2571.     if (fseek (fp, rcs->delta_pos, SEEK_SET) != 0)
  2572.         error (1, 0, "cannot fseek RCS file");
  2573.     }
  2574.  
  2575.     ishead = 1;
  2576.     vers = NULL;
  2577.     prev_vers = NULL;
  2578.     trunk_vers = NULL;
  2579.     next = NULL;
  2580.     onbranch = 0;
  2581.     foundhead = 0;
  2582.  
  2583.     linevector_init (&curlines);
  2584.     linevector_init (&headlines);
  2585.     linevector_init (&trunklines);
  2586.  
  2587.     /* We set BRANCHVERSION to the version we are currently looking
  2588.        for.  Initially, this is the version on the trunk from which
  2589.        VERSION branches off.  If VERSION is not a branch, then
  2590.        BRANCHVERSION is just VERSION.  */
  2591.     branchversion = xstrdup (version);
  2592.     cpversion = strchr (branchversion, '.');
  2593.     if (cpversion != NULL)
  2594.         cpversion = strchr (cpversion + 1, '.');
  2595.     if (cpversion != NULL)
  2596.         *cpversion = '\0';
  2597.  
  2598.     do {
  2599.     getrcsrev (fp, &key);
  2600.  
  2601.     if (next != NULL && strcmp (next, key) != 0)
  2602.     {
  2603.         /* This is not the next version we need.  It is a branch
  2604.                version which we want to ignore.  */
  2605.         isnext = 0;
  2606.         isversion = 0;
  2607.     }
  2608.     else
  2609.     {
  2610.         isnext = 1;
  2611.  
  2612.         /* look up the revision */
  2613.         node = findnode (rcs->versions, key);
  2614.         if (node == NULL)
  2615.             error (1, 0,
  2616.                "mismatch in rcs file %s between deltas and deltatexts",
  2617.                rcs->path);
  2618.  
  2619.         /* Stash the previous version.  */
  2620.         prev_vers = vers;
  2621.  
  2622.         vers = (RCSVers *) node->data;
  2623.         next = vers->next;
  2624.  
  2625.         /* Compare key and trunkversion now, because key points to
  2626.            storage controlled by getrcskey.  */
  2627.         if (strcmp (branchversion, key) == 0)
  2628.             isversion = 1;
  2629.         else
  2630.             isversion = 0;
  2631.     }
  2632.  
  2633.     while ((n = getrcskey (fp, &key, &value, &vallen)) >= 0)
  2634.     {
  2635.         if (strcmp (key, "text") == 0)
  2636.         {
  2637.         if (ishead)
  2638.         {
  2639.             char *p;
  2640.  
  2641.             p = block_alloc (vallen);
  2642.             memcpy (p, value, vallen);
  2643.  
  2644.             linevector_add (&curlines, p, vallen, NULL, 0);
  2645.             ishead = 0;
  2646.         }
  2647.         else if (isnext)
  2648.         {
  2649.             char *p;
  2650.             char *q;
  2651.             int op;
  2652.             /* The RCS format throws us for a loop in that the
  2653.                deltafrags (if we define a deltafrag as an
  2654.                add or a delete) need to be applied in reverse
  2655.                order.  So we stick them into a linked list.  */
  2656.             struct deltafrag {
  2657.             enum {ADD, DELETE} type;
  2658.             unsigned long pos;
  2659.             unsigned long nlines;
  2660.             char *new_lines;
  2661.                 size_t len;
  2662.             struct deltafrag *next;
  2663.             };
  2664.             struct deltafrag *dfhead;
  2665.             struct deltafrag *df;
  2666.  
  2667.             dfhead = NULL;
  2668.             for (p = value; p != NULL && p < value + vallen; )
  2669.             {
  2670.             op = *p++;
  2671.             if (op != 'a' && op != 'd')
  2672.                 /* Can't just skip over the deltafrag, because
  2673.                    the value of op determines the syntax.  */
  2674.                 error (1, 0, "unrecognized operation '%c' in %s",
  2675.                    op, rcs->path);
  2676.             df = (struct deltafrag *)
  2677.                 xmalloc (sizeof (struct deltafrag));
  2678.             df->next = dfhead;
  2679.             dfhead = df;
  2680.             df->pos = strtoul (p, &q, 10);
  2681.  
  2682.             if (p == q)
  2683.                 error (1, 0, "number expected in %s",
  2684.                    rcs->path);
  2685.             p = q;
  2686.             if (*p++ != ' ')
  2687.                 error (1, 0, "space expected in %s",
  2688.                    rcs->path);
  2689.             df->nlines = strtoul (p, &q, 10);
  2690.             if (p == q)
  2691.                 error (1, 0, "number expected in %s",
  2692.                    rcs->path);
  2693.             p = q;
  2694.             if (*p++ != '\012')
  2695.                 error (1, 0, "linefeed expected in %s",
  2696.                    rcs->path);
  2697.  
  2698.             if (op == 'a')
  2699.             {
  2700.                 unsigned int i;
  2701.  
  2702.                 df->type = ADD;
  2703.                 i = df->nlines;
  2704.                 /* The text we want is the number of lines
  2705.                    specified, or until the end of the value,
  2706.                    whichever comes first (it will be the former
  2707.                    except in the case where we are adding a line
  2708.                    which does not end in newline).  */
  2709.                 for (q = p; i != 0; ++q)
  2710.                 if (*q == '\n')
  2711.                     --i;
  2712.                 else if (q == value + vallen)
  2713.                 {
  2714.                     if (i != 1)
  2715.                     error (1, 0, "\
  2716. invalid rcs file %s: premature end of value",
  2717.                            rcs->path);
  2718.                     else
  2719.                     break;
  2720.                 }
  2721.  
  2722.                 /* Copy the text we are adding into allocated
  2723.                    space.  */
  2724.                 df->new_lines = block_alloc (q - p);
  2725.                 memcpy (df->new_lines, p, q - p);
  2726.                 df->len = q - p;
  2727.  
  2728.                 p = q;
  2729.             }
  2730.             else
  2731.             {
  2732.                 /* Correct for the fact that line numbers in RCS
  2733.                    files start with 1.  */
  2734.                 --df->pos;
  2735.  
  2736.                 assert (op == 'd');
  2737.                 df->type = DELETE;
  2738.             }
  2739.             }
  2740.             for (df = dfhead; df != NULL;)
  2741.             {
  2742.             unsigned int ln;
  2743.  
  2744.             switch (df->type)
  2745.             {
  2746.             case ADD:
  2747.                 linevector_add (&curlines, df->new_lines,
  2748.                         df->len,
  2749.                         onbranch ? vers : NULL,
  2750.                         df->pos);
  2751.                 break;
  2752.             case DELETE:
  2753.                 if (df->pos > curlines.nlines
  2754.                 || df->pos + df->nlines > curlines.nlines)
  2755.                 error (1, 0, "\
  2756. invalid rcs file %s (`d' operand out of range)",
  2757.                        rcs->path);
  2758.                 if (! onbranch)
  2759.                     for (ln = df->pos;
  2760.                      ln < df->pos + df->nlines;
  2761.                      ++ln)
  2762.                     curlines.vector[ln]->vers = prev_vers;
  2763.                 linevector_delete (&curlines, df->pos, df->nlines);
  2764.                 break;
  2765.             }
  2766.                 df = df->next;
  2767.             free (dfhead);
  2768.             dfhead = df;
  2769.             }
  2770.         }
  2771.         break;
  2772.         }
  2773.     }
  2774.     if (n < 0)
  2775.         goto l_error;
  2776.  
  2777.     if (isversion)
  2778.     {
  2779.         /* This is either the version we want, or it is the
  2780.                branchpoint to the version we want.  */
  2781.         if (strcmp (branchversion, version) == 0)
  2782.         {
  2783.             /* This is the version we want.  */
  2784.         linevector_copy (&headlines, &curlines);
  2785.         foundhead = 1;
  2786.         if (onbranch)
  2787.         {
  2788.             /* We have found this version by tracking up a
  2789.                        branch.  Restore back to the lines we saved
  2790.                        when we left the trunk, and continue tracking
  2791.                        down the trunk.  */
  2792.             onbranch = 0;
  2793.             vers = trunk_vers;
  2794.             next = vers->next;
  2795.             linevector_copy (&curlines, &trunklines);
  2796.         }
  2797.         }
  2798.         else
  2799.         {
  2800.             Node *p;
  2801.  
  2802.             /* We need to look up the branch.  */
  2803.             onbranch = 1;
  2804.  
  2805.         if (numdots (branchversion) < 2)
  2806.         {
  2807.             unsigned int ln;
  2808.  
  2809.             /* We are leaving the trunk; save the current
  2810.                        lines so that we can restore them when we
  2811.                        continue tracking down the trunk.  */
  2812.             trunk_vers = vers;
  2813.             linevector_copy (&trunklines, &curlines);
  2814.  
  2815.             /* Reset the version information we have
  2816.                        accumulated so far.  It only applies to the
  2817.                        changes from the head to this version.  */
  2818.             for (ln = 0; ln < curlines.nlines; ++ln)
  2819.                 curlines.vector[ln]->vers = NULL;
  2820.         }
  2821.  
  2822.         /* The next version we want is the entry on
  2823.                    NODE->branches whose prefix is BRANCHVERSION.  */
  2824.         if (vers->branches == NULL)
  2825.             error (1, 0, "missing expected branches in %s",
  2826.                rcs->path);
  2827.         *cpversion = '.';
  2828.         ++cpversion;
  2829.         for (p = vers->branches->list->next;
  2830.              p != vers->branches->list;
  2831.              p = p->next)
  2832.             if (strncmp (p->key, branchversion,
  2833.                  cpversion - branchversion) == 0)
  2834.             break;
  2835.         if (p == vers->branches->list)
  2836.             error (1, 0, "missing expected branch in %s",
  2837.                rcs->path);
  2838.  
  2839.         next = p->key;
  2840.  
  2841.         cpversion = strchr (cpversion, '.');
  2842.         if (cpversion == NULL)
  2843.             error (1, 0, "version number confusion in %s",
  2844.                rcs->path);
  2845.         cpversion = strchr (cpversion + 1, '.');
  2846.         if (cpversion != NULL)
  2847.             *cpversion = '\0';
  2848.         }
  2849.     }
  2850.     if (op == RCS_FETCH && foundhead)
  2851.         break;
  2852.     } while (next != NULL);
  2853.  
  2854.     if (fclose (fp) < 0)
  2855.     error (0, errno, "cannot close %s", rcs->path);
  2856.  
  2857.     if (! foundhead)
  2858.         error (1, 0, "could not find desired version %s in %s",
  2859.            version, rcs->path);
  2860.  
  2861.     /* Now print out or return the data we have just computed.  */
  2862.     switch (op)
  2863.     {
  2864.     case RCS_ANNOTATE:
  2865.         {
  2866.         unsigned int ln;
  2867.  
  2868.         for (ln = 0; ln < headlines.nlines; ++ln)
  2869.         {
  2870.             char buf[80];
  2871.             /* Period which separates year from month in date.  */
  2872.             char *ym;
  2873.             /* Period which separates month from day in date.  */
  2874.             char *md;
  2875.             RCSVers *prvers;
  2876.  
  2877.             prvers = headlines.vector[ln]->vers;
  2878.             if (prvers == NULL)
  2879.             prvers = vers;
  2880.  
  2881.             sprintf (buf, "%-12s (%-8.8s ",
  2882.                  prvers->version,
  2883.                  prvers->author);
  2884.             cvs_output (buf, 0);
  2885.  
  2886.             /* Now output the date.  */
  2887.             ym = strchr (prvers->date, '.');
  2888.             if (ym == NULL)
  2889.             cvs_output ("??-???-??", 0);
  2890.             else
  2891.             {
  2892.             md = strchr (ym + 1, '.');
  2893.             if (md == NULL)
  2894.                 cvs_output ("??", 0);
  2895.             else
  2896.                 cvs_output (md + 1, 2);
  2897.  
  2898.             cvs_output ("-", 1);
  2899.             cvs_output (month_printname (ym + 1), 0);
  2900.             cvs_output ("-", 1);
  2901.             /* Only output the last two digits of the year.  Our output
  2902.                lines are long enough as it is without printing the
  2903.                century.  */
  2904.             cvs_output (ym - 2, 2);
  2905.             }
  2906.             cvs_output ("): ", 0);
  2907.             cvs_output (headlines.vector[ln]->text,
  2908.                 headlines.vector[ln]->len);
  2909.             cvs_output ("\n", 1);
  2910.         }
  2911.         }
  2912.         break;
  2913.     case RCS_FETCH:
  2914.         {
  2915.         char *p;
  2916.         size_t n;
  2917.         unsigned int ln;
  2918.  
  2919.         assert (text != NULL);
  2920.         assert (len != NULL);
  2921.  
  2922.         n = 0;
  2923.         for (ln = 0; ln < headlines.nlines; ++ln)
  2924.             /* 1 for \n */
  2925.             n += headlines.vector[ln]->len + 1;
  2926.         p = xmalloc (n);
  2927.         *text = p;
  2928.         for (ln = 0; ln < headlines.nlines; ++ln)
  2929.         {
  2930.             memcpy (p, headlines.vector[ln]->text,
  2931.                 headlines.vector[ln]->len);
  2932.             p += headlines.vector[ln]->len;
  2933.             if (headlines.vector[ln]->has_newline)
  2934.             *p++ = '\n';
  2935.         }
  2936.         *len = p - *text;
  2937.         assert (*len <= n);
  2938.         }
  2939.         break;
  2940.     }
  2941.  
  2942.     linevector_free (&curlines);
  2943.     linevector_free (&headlines);
  2944.     linevector_free (&trunklines);
  2945.  
  2946.     block_free ();
  2947.     return;
  2948.  
  2949.   l_error:
  2950.     if (ferror (fp))
  2951.     error (1, errno, "cannot read %s", rcs->path);
  2952.     else
  2953.         error (1, 0, "%s does not appear to be a valid rcs file",
  2954.            rcs->path);
  2955. }
  2956.  
  2957.  
  2958. /* Annotate command.  In rcs.c for historical reasons (from back when
  2959.    what is now RCS_deltas was part of annotate_fileproc).  */
  2960.  
  2961. /* Options from the command line.  */
  2962.  
  2963. static int force_tag_match = 1;
  2964. static char *tag = NULL;
  2965. static char *date = NULL;
  2966.  
  2967. static int annotate_fileproc PROTO ((void *callerdat, struct file_info *));
  2968.  
  2969. static int
  2970. annotate_fileproc (callerdat, finfo)
  2971.     void *callerdat;
  2972.     struct file_info *finfo;
  2973. {
  2974.     FILE *fp = NULL;
  2975.     char *version;
  2976.  
  2977.     if (finfo->rcs == NULL)
  2978.         return (1);
  2979.  
  2980.     if (finfo->rcs->flags & PARTIAL)
  2981.         RCS_reparsercsfile (finfo->rcs, 0, &fp);
  2982.  
  2983.     version = RCS_getversion (finfo->rcs, tag, date, force_tag_match, 0);
  2984.     if (version == NULL)
  2985.         return 0;
  2986.  
  2987.     /* Distinguish output for various files if we are processing
  2988.        several files.  */
  2989.     cvs_outerr ("Annotations for ", 0);
  2990.     cvs_outerr (finfo->fullname, 0);
  2991.     cvs_outerr ("\n***************\n", 0);
  2992.  
  2993.     RCS_deltas (finfo->rcs, fp, version, RCS_ANNOTATE, NULL, NULL);
  2994.     free (version);
  2995.     return 0;
  2996. }
  2997.  
  2998. static const char *const annotate_usage[] =
  2999. {
  3000.     "Usage: %s %s [-lf] [-r rev|-D date] [files...]\n",
  3001.     "\t-l\tLocal directory only, no recursion.\n",
  3002.     "\t-f\tUse head revision if tag/date not found.\n",
  3003.     "\t-r rev\tAnnotate file as of specified revision/tag.\n",
  3004.     "\t-D date\tAnnotate file as of specified date.\n",
  3005.     NULL
  3006. };
  3007.  
  3008. /* Command to show the revision, date, and author where each line of a
  3009.    file was modified.  */
  3010.  
  3011. int
  3012. annotate (argc, argv)
  3013.     int argc;
  3014.     char **argv;
  3015. {
  3016.     int local = 0;
  3017.     int c;
  3018.  
  3019.     if (argc == -1)
  3020.     usage (annotate_usage);
  3021.  
  3022.     optind = 0;
  3023.     while ((c = getopt (argc, argv, "+lr:D:f")) != -1)
  3024.     {
  3025.     switch (c)
  3026.     {
  3027.         case 'l':
  3028.         local = 1;
  3029.         break;
  3030.         case 'r':
  3031.             tag = optarg;
  3032.         break;
  3033.         case 'D':
  3034.             date = Make_Date (optarg);
  3035.         break;
  3036.         case 'f':
  3037.             force_tag_match = 0;
  3038.         break;
  3039.         case '?':
  3040.         default:
  3041.         usage (annotate_usage);
  3042.         break;
  3043.     }
  3044.     }
  3045.     argc -= optind;
  3046.     argv += optind;
  3047.  
  3048. #ifdef CLIENT_SUPPORT
  3049.     if (client_active)
  3050.     {
  3051.     start_server ();
  3052.     ign_setup ();
  3053.  
  3054.     if (local)
  3055.         send_arg ("-l");
  3056.     if (!force_tag_match)
  3057.         send_arg ("-f");
  3058.     option_with_arg ("-r", tag);
  3059.     if (date)
  3060.         client_senddate (date);
  3061.     send_file_names (argc, argv, SEND_EXPAND_WILD);
  3062.     /* FIXME:  We shouldn't have to send current files, but I'm not sure
  3063.        whether it works.  So send the files --
  3064.        it's slower but it works.  */
  3065.     send_files (argc, argv, local, 0);
  3066.     send_to_server ("annotate\012", 0);
  3067.     return get_responses_and_close ();
  3068.     }
  3069. #endif /* CLIENT_SUPPORT */
  3070.  
  3071.     return start_recursion (annotate_fileproc, (FILESDONEPROC) NULL,
  3072.                 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
  3073.                 argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
  3074.                 1);
  3075. }
  3076.