log.c revision 175273
117721Speter/*
2175273Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3175273Sobrien *
4175273Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175273Sobrien *                                  and others.
6175273Sobrien *
7175273Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175273Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
917721Speter *
1017721Speter * You may distribute under the terms of the GNU General Public License as
1132785Speter * specified in the README file that comes with the CVS source distribution.
1217721Speter *
1317721Speter * Print Log Information
1417721Speter *
1517721Speter * Prints the RCS "log" (rlog) information for the specified files.  With no
1617721Speter * argument, prints the log information for all the files in the directory
1717721Speter * (recursive by default).
18131688Sdes *
19131688Sdes * $FreeBSD: head/contrib/cvs/src/log.c 175273 2008-01-13 06:08:49Z obrien $
2017721Speter */
2117721Speter
2217721Speter#include "cvs.h"
23175273Sobrien#include <assert.h>
2417721Speter
2525839Speter/* This structure holds information parsed from the -r option.  */
2617721Speter
2725839Speterstruct option_revlist
2825839Speter{
2925839Speter    /* The next -r option.  */
3025839Speter    struct option_revlist *next;
3125839Speter    /* The first revision to print.  This is NULL if the range is
3225839Speter       :rev, or if no revision is given.  */
3325839Speter    char *first;
3425839Speter    /* The last revision to print.  This is NULL if the range is rev:,
3525839Speter       or if no revision is given.  If there is no colon, first and
3625839Speter       last are the same.  */
3725839Speter    char *last;
3825839Speter    /* Nonzero if there was a trailing `.', which means to print only
3925839Speter       the head revision of a branch.  */
4025839Speter    int branchhead;
4181404Speter    /* Nonzero if first and last are inclusive.  */
4281404Speter    int inclusive;
4325839Speter};
4425839Speter
4525839Speter/* This structure holds information derived from option_revlist given
4625839Speter   a particular RCS file.  */
4725839Speter
4825839Speterstruct revlist
4925839Speter{
5025839Speter    /* The next pair.  */
5125839Speter    struct revlist *next;
5225839Speter    /* The first numeric revision to print.  */
5325839Speter    char *first;
5425839Speter    /* The last numeric revision to print.  */
5525839Speter    char *last;
5625839Speter    /* The number of fields in these revisions (one more than
5725839Speter       numdots).  */
5825839Speter    int fields;
5981404Speter    /* Whether first & last are to be included or excluded.  */
6081404Speter    int inclusive;
6125839Speter};
6225839Speter
6325839Speter/* This structure holds information parsed from the -d option.  */
6425839Speter
6525839Speterstruct datelist
6625839Speter{
6725839Speter    /* The next date.  */
6825839Speter    struct datelist *next;
6925839Speter    /* The starting date.  */
7025839Speter    char *start;
7125839Speter    /* The ending date.  */
7225839Speter    char *end;
7325839Speter    /* Nonzero if the range is inclusive rather than exclusive.  */
7425839Speter    int inclusive;
7525839Speter};
7625839Speter
7725839Speter/* This structure is used to pass information through start_recursion.  */
7825839Speterstruct log_data
7925839Speter{
8025839Speter    /* Nonzero if the -R option was given, meaning that only the name
8125839Speter       of the RCS file should be printed.  */
8225839Speter    int nameonly;
8325839Speter    /* Nonzero if the -h option was given, meaning that only header
8425839Speter       information should be printed.  */
8525839Speter    int header;
8625839Speter    /* Nonzero if the -t option was given, meaning that only the
8725839Speter       header and the descriptive text should be printed.  */
8825839Speter    int long_header;
8925839Speter    /* Nonzero if the -N option was seen, meaning that tag information
9025839Speter       should not be printed.  */
9125839Speter    int notags;
9225839Speter    /* Nonzero if the -b option was seen, meaning that only revisions
9325839Speter       on the default branch should be printed.  */
9425839Speter    int default_branch;
95102840Speter    /* Nonzero if the -S option was seen, meaning that the header/name
96102840Speter       should be suppressed if no revisions are selected.  */
97102840Speter    int sup_header;
9825839Speter    /* If not NULL, the value given for the -r option, which lists
9925839Speter       sets of revisions to be printed.  */
10025839Speter    struct option_revlist *revlist;
10125839Speter    /* If not NULL, the date pairs given for the -d option, which
10225839Speter       select date ranges to print.  */
10325839Speter    struct datelist *datelist;
10425839Speter    /* If not NULL, the single dates given for the -d option, which
10525839Speter       select specific revisions to print based on a date.  */
10625839Speter    struct datelist *singledatelist;
10725839Speter    /* If not NULL, the list of states given for the -s option, which
10825839Speter       only prints revisions of given states.  */
10925839Speter    List *statelist;
11025839Speter    /* If not NULL, the list of login names given for the -w option,
11125839Speter       which only prints revisions checked in by given users.  */
11225839Speter    List *authorlist;
11325839Speter};
11425839Speter
11525839Speter/* This structure is used to pass information through walklist.  */
11625839Speterstruct log_data_and_rcs
11725839Speter{
11825839Speter    struct log_data *log_data;
11925839Speter    struct revlist *revlist;
12025839Speter    RCSNode *rcs;
12125839Speter};
12225839Speter
12381404Speterstatic int rlog_proc PROTO((int argc, char **argv, char *xwhere,
12481404Speter			    char *mwhere, char *mfile, int shorten,
12581404Speter			    int local_specified, char *mname, char *msg));
126128266Speterstatic Dtype log_dirproc PROTO ((void *callerdat, const char *dir,
127128266Speter                                 const char *repository,
128128266Speter                                 const char *update_dir,
129128266Speter                                 List *entries));
13025839Speterstatic int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
13125839Speterstatic struct option_revlist *log_parse_revlist PROTO ((const char *));
13225839Speterstatic void log_parse_date PROTO ((struct log_data *, const char *));
13325839Speterstatic void log_parse_list PROTO ((List **, const char *));
134175273Sobrienstatic struct revlist *log_expand_revlist PROTO ((RCSNode *, char *,
13525839Speter						  struct option_revlist *,
13625839Speter						  int));
13725839Speterstatic void log_free_revlist PROTO ((struct revlist *));
13825839Speterstatic int log_version_requested PROTO ((struct log_data *, struct revlist *,
13925839Speter					 RCSNode *, RCSVers *));
14025839Speterstatic int log_symbol PROTO ((Node *, void *));
14125839Speterstatic int log_count PROTO ((Node *, void *));
14225839Speterstatic int log_fix_singledate PROTO ((Node *, void *));
14325839Speterstatic int log_count_print PROTO ((Node *, void *));
14425839Speterstatic void log_tree PROTO ((struct log_data *, struct revlist *,
14525839Speter			     RCSNode *, const char *));
14625839Speterstatic void log_abranch PROTO ((struct log_data *, struct revlist *,
14725839Speter				RCSNode *, const char *));
14825839Speterstatic void log_version PROTO ((struct log_data *, struct revlist *,
14925839Speter				RCSNode *, RCSVers *, int));
15025839Speterstatic int log_branch PROTO ((Node *, void *));
15125839Speterstatic int version_compare PROTO ((const char *, const char *, int));
15225839Speter
15381404Speterstatic struct log_data log_data;
15481404Speterstatic int is_rlog;
15581404Speter
15617721Speterstatic const char *const log_usage[] =
15717721Speter{
15825839Speter    "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
15925839Speter    "    [-w[logins]] [files...]\n",
16017721Speter    "\t-l\tLocal directory only, no recursion.\n",
161175273Sobrien    "\t-b\tOnly list revisions on the default branch.\n",
162175273Sobrien    "\t-h\tOnly print header.\n",
16325839Speter    "\t-R\tOnly print name of RCS file.\n",
16425839Speter    "\t-t\tOnly print header and descriptive text.\n",
16525839Speter    "\t-N\tDo not list tags.\n",
166175273Sobrien    "\t-S\tDo not print name/header if no revisions selected.  -d, -r,\n",
167175273Sobrien    "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
168175273Sobrien    "\t\t-t without this option.\n",
169102840Speter    "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
17081404Speter    "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
171102840Speter    "\t   rev1::rev2  Between rev1 and rev2, excluding rev1.\n",
17281404Speter    "\t   rev:        rev and following revisions on the same branch.\n",
17381404Speter    "\t   rev::       After rev on the same branch.\n",
17481404Speter    "\t   :rev        rev and previous revisions on the same branch.\n",
175102840Speter    "\t   ::rev       rev and previous revisions on the same branch.\n",
17681404Speter    "\t   rev         Just rev.\n",
17781404Speter    "\t   branch      All revisions on the branch.\n",
17881404Speter    "\t   branch.     The last revision on the branch.\n",
179102840Speter    "\t-d dates\tA semicolon-separated list of dates\n",
180102840Speter    "\t        \t(D1<D2 for range, D for latest before).\n",
18125839Speter    "\t-s states\tOnly list revisions with specified states.\n",
18225839Speter    "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
18332785Speter    "(Specify the --help global option for a list of other help options)\n",
18417721Speter    NULL
18517721Speter};
18617721Speter
18766525Speter#ifdef CLIENT_SUPPORT
18866525Speter
18966525Speter/* Helper function for send_arg_list.  */
19066525Speterstatic int send_one PROTO ((Node *, void *));
19166525Speter
19266525Speterstatic int
19366525Spetersend_one (node, closure)
19466525Speter    Node *node;
19566525Speter    void *closure;
19666525Speter{
19766525Speter    char *option = (char *) closure;
19866525Speter
19966525Speter    send_to_server ("Argument ", 0);
20066525Speter    send_to_server (option, 0);
20166525Speter    if (strcmp (node->key, "@@MYSELF") == 0)
20266525Speter	/* It is a bare -w option.  Note that we must send it as
20366525Speter	   -w rather than messing with getcaller() or something (which on
20466525Speter	   the client will return garbage).  */
20566525Speter	;
20666525Speter    else
20766525Speter	send_to_server (node->key, 0);
20866525Speter    send_to_server ("\012", 0);
20966525Speter    return 0;
21066525Speter}
21166525Speter
21266525Speter/* For each element in ARG, send an argument consisting of OPTION
21366525Speter   concatenated with that element.  */
21466525Speterstatic void send_arg_list PROTO ((char *, List *));
21566525Speter
21666525Speterstatic void
21766525Spetersend_arg_list (option, arg)
21866525Speter    char *option;
21966525Speter    List *arg;
22066525Speter{
22166525Speter    if (arg == NULL)
22266525Speter	return;
22366525Speter    walklist (arg, send_one, (void *)option);
22466525Speter}
22566525Speter
22666525Speter#endif
22766525Speter
22817721Speterint
22917721Spetercvslog (argc, argv)
23017721Speter    int argc;
23117721Speter    char **argv;
23217721Speter{
23325839Speter    int c;
23417721Speter    int err = 0;
23517721Speter    int local = 0;
23666525Speter    struct option_revlist **prl;
23717721Speter
238128266Speter    is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
23981404Speter
24017721Speter    if (argc == -1)
24117721Speter	usage (log_usage);
24217721Speter
24325839Speter    memset (&log_data, 0, sizeof log_data);
24466525Speter    prl = &log_data.revlist;
24517721Speter
24626065Speter    optind = 0;
247165111Sobrien    while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1)
24825839Speter    {
24925839Speter	switch (c)
25025839Speter	{
25125839Speter	    case 'b':
25225839Speter		log_data.default_branch = 1;
25325839Speter		break;
25425839Speter	    case 'd':
25525839Speter		log_parse_date (&log_data, optarg);
25625839Speter		break;
25725839Speter	    case 'h':
25825839Speter		log_data.header = 1;
25925839Speter		break;
26025839Speter	    case 'l':
26125839Speter		local = 1;
26225839Speter		break;
26325839Speter	    case 'N':
26425839Speter		log_data.notags = 1;
26525839Speter		break;
266165111Sobrien	    case 'n':
267165111Sobrien		log_data.notags = 0;
268165111Sobrien		break;
269102840Speter	    case 'S':
270102840Speter		log_data.sup_header = 1;
271102840Speter		break;
27225839Speter	    case 'R':
27325839Speter		log_data.nameonly = 1;
27425839Speter		break;
27525839Speter	    case 'r':
27666525Speter		*prl = log_parse_revlist (optarg);
27766525Speter		prl = &(*prl)->next;
27825839Speter		break;
27925839Speter	    case 's':
28025839Speter		log_parse_list (&log_data.statelist, optarg);
28125839Speter		break;
28225839Speter	    case 't':
28325839Speter		log_data.long_header = 1;
28425839Speter		break;
28525839Speter	    case 'w':
28625839Speter		if (optarg != NULL)
28725839Speter		    log_parse_list (&log_data.authorlist, optarg);
28825839Speter		else
28966525Speter		    log_parse_list (&log_data.authorlist, "@@MYSELF");
29025839Speter		break;
29125839Speter	    case '?':
29225839Speter	    default:
29325839Speter		usage (log_usage);
29425839Speter		break;
29525839Speter	}
29625839Speter    }
29781404Speter    argc -= optind;
29881404Speter    argv += optind;
29925839Speter
30017721Speter    wrap_setup ();
30117721Speter
30217721Speter#ifdef CLIENT_SUPPORT
30381404Speter    if (current_parsed_root->isremote)
30425839Speter    {
30566525Speter	struct datelist *p;
30666525Speter	struct option_revlist *rp;
30766525Speter	char datetmp[MAXDATELEN];
30825839Speter
30917721Speter	/* We're the local client.  Fire up the remote server.  */
31017721Speter	start_server ();
31181404Speter
31281404Speter	if (is_rlog && !supported_request ("rlog"))
31381404Speter	    error (1, 0, "server does not support rlog");
31481404Speter
31517721Speter	ign_setup ();
31617721Speter
31766525Speter	if (log_data.default_branch)
31866525Speter	    send_arg ("-b");
31917721Speter
32066525Speter	while (log_data.datelist != NULL)
32166525Speter	{
32266525Speter	    p = log_data.datelist;
32366525Speter	    log_data.datelist = p->next;
324175273Sobrien	    assert (p->start != NULL && p->end != NULL);
32566525Speter	    send_to_server ("Argument -d\012", 0);
32666525Speter	    send_to_server ("Argument ", 0);
32766525Speter	    date_to_internet (datetmp, p->start);
32866525Speter	    send_to_server (datetmp, 0);
32966525Speter	    if (p->inclusive)
33066525Speter		send_to_server ("<=", 0);
33166525Speter	    else
33266525Speter		send_to_server ("<", 0);
33366525Speter	    date_to_internet (datetmp, p->end);
33466525Speter	    send_to_server (datetmp, 0);
33566525Speter	    send_to_server ("\012", 0);
336175273Sobrien	    free (p->start);
337175273Sobrien	    free (p->end);
33866525Speter	    free (p);
33966525Speter	}
34066525Speter	while (log_data.singledatelist != NULL)
34166525Speter	{
34266525Speter	    p = log_data.singledatelist;
34366525Speter	    log_data.singledatelist = p->next;
344175273Sobrien	    assert (p->end != NULL);
34566525Speter	    send_to_server ("Argument -d\012", 0);
34666525Speter	    send_to_server ("Argument ", 0);
34766525Speter	    date_to_internet (datetmp, p->end);
34866525Speter	    send_to_server (datetmp, 0);
34966525Speter	    send_to_server ("\012", 0);
350175273Sobrien	    free (p->end);
35166525Speter	    free (p);
35266525Speter	}
35366525Speter
35466525Speter	if (log_data.header)
35566525Speter	    send_arg ("-h");
35666525Speter	if (local)
35766525Speter	    send_arg("-l");
35866525Speter	if (log_data.notags)
35966525Speter	    send_arg("-N");
360102840Speter	if (log_data.sup_header)
361102840Speter	    send_arg("-S");
36266525Speter	if (log_data.nameonly)
36366525Speter	    send_arg("-R");
36466525Speter	if (log_data.long_header)
36566525Speter	    send_arg("-t");
36617721Speter
36766525Speter	while (log_data.revlist != NULL)
36866525Speter	{
36966525Speter	    rp = log_data.revlist;
37066525Speter	    log_data.revlist = rp->next;
37166525Speter	    send_to_server ("Argument -r", 0);
37266525Speter	    if (rp->branchhead)
37366525Speter	    {
37466525Speter		if (rp->first != NULL)
37566525Speter		    send_to_server (rp->first, 0);
37666525Speter		send_to_server (".", 1);
37766525Speter	    }
37866525Speter	    else
37966525Speter	    {
38066525Speter		if (rp->first != NULL)
38166525Speter		    send_to_server (rp->first, 0);
38266525Speter		send_to_server (":", 1);
38381404Speter		if (!rp->inclusive)
38481404Speter		    send_to_server (":", 1);
38566525Speter		if (rp->last != NULL)
38666525Speter		    send_to_server (rp->last, 0);
38766525Speter	    }
38866525Speter	    send_to_server ("\012", 0);
38966525Speter	    if (rp->first)
39066525Speter		free (rp->first);
39166525Speter	    if (rp->last)
39266525Speter		free (rp->last);
39366525Speter	    free (rp);
39466525Speter	}
39566525Speter	send_arg_list ("-s", log_data.statelist);
39666525Speter	dellist (&log_data.statelist);
39766525Speter	send_arg_list ("-w", log_data.authorlist);
39866525Speter	dellist (&log_data.authorlist);
399107484Speter	send_arg ("--");
40066525Speter
40181404Speter	if (is_rlog)
40281404Speter	{
40381404Speter	    int i;
40481404Speter	    for (i = 0; i < argc; i++)
40581404Speter		send_arg (argv[i]);
40681404Speter	    send_to_server ("rlog\012", 0);
40781404Speter	}
40881404Speter	else
40981404Speter	{
41081404Speter	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
41181404Speter	    send_file_names (argc, argv, SEND_EXPAND_WILD);
41281404Speter	    send_to_server ("log\012", 0);
41381404Speter	}
41417721Speter        err = get_responses_and_close ();
41517721Speter	return err;
41617721Speter    }
41717721Speter#endif
41817721Speter
41966525Speter    /* OK, now that we know we are local/server, we can resolve @@MYSELF
42066525Speter       into our user name.  */
42166525Speter    if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
42266525Speter	log_parse_list (&log_data.authorlist, getcaller ());
42366525Speter
42481404Speter    if (is_rlog)
42581404Speter    {
42681404Speter	DBM *db;
42781404Speter	int i;
42881404Speter	db = open_module ();
42981404Speter	for (i = 0; i < argc; i++)
43081404Speter	{
43181404Speter	    err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
432102840Speter			     (char *) NULL, 0, local, 0, 0, (char *) NULL);
43381404Speter	}
43481404Speter	close_module (db);
43581404Speter    }
43681404Speter    else
43781404Speter    {
43881404Speter	err = rlog_proc (argc + 1, argv - 1, (char *) NULL,
439102840Speter			 (char *) NULL, (char *) NULL, 0, local, (char *) NULL,
44081404Speter			 (char *) NULL);
44181404Speter    }
44266525Speter
44366525Speter    while (log_data.revlist)
44466525Speter    {
44566525Speter	struct option_revlist *rl = log_data.revlist->next;
44666525Speter	if (log_data.revlist->first)
44766525Speter	    free (log_data.revlist->first);
44866525Speter	if (log_data.revlist->last)
44966525Speter	    free (log_data.revlist->last);
45066525Speter	free (log_data.revlist);
45166525Speter	log_data.revlist = rl;
45266525Speter    }
45366525Speter    while (log_data.datelist)
45466525Speter    {
45566525Speter	struct datelist *nd = log_data.datelist->next;
45666525Speter	if (log_data.datelist->start)
45766525Speter	    free (log_data.datelist->start);
45866525Speter	if (log_data.datelist->end)
45966525Speter	    free (log_data.datelist->end);
46066525Speter	free (log_data.datelist);
46166525Speter	log_data.datelist = nd;
46266525Speter    }
46366525Speter    while (log_data.singledatelist)
46466525Speter    {
46566525Speter	struct datelist *nd = log_data.singledatelist->next;
46666525Speter	if (log_data.singledatelist->start)
46766525Speter	    free (log_data.singledatelist->start);
46866525Speter	if (log_data.singledatelist->end)
46966525Speter	    free (log_data.singledatelist->end);
47066525Speter	free (log_data.singledatelist);
47166525Speter	log_data.singledatelist = nd;
47266525Speter    }
47366525Speter    dellist (&log_data.statelist);
47466525Speter    dellist (&log_data.authorlist);
47566525Speter
47617721Speter    return (err);
47717721Speter}
47817721Speter
47981404Speter
48081404Speterstatic int
48181404Speterrlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
48281404Speter    int argc;
48381404Speter    char **argv;
48481404Speter    char *xwhere;
48581404Speter    char *mwhere;
48681404Speter    char *mfile;
48781404Speter    int shorten;
48881404Speter    int local;
48981404Speter    char *mname;
49081404Speter    char *msg;
49181404Speter{
49281404Speter    /* Begin section which is identical to patch_proc--should this
49381404Speter       be abstracted out somehow?  */
49481404Speter    char *myargv[2];
49581404Speter    int err = 0;
49681404Speter    int which;
49781404Speter    char *repository;
49881404Speter    char *where;
49981404Speter
50081404Speter    if (is_rlog)
50181404Speter    {
502128266Speter	repository = xmalloc (strlen (current_parsed_root->directory)
503128266Speter                              + strlen (argv[0])
50481404Speter			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
505128266Speter	(void)sprintf (repository, "%s/%s",
506128266Speter                       current_parsed_root->directory, argv[0]);
507128266Speter	where = xmalloc (strlen (argv[0])
508128266Speter                         + (mfile == NULL ? 0 : strlen (mfile) + 1)
50981404Speter			 + 1);
51081404Speter	(void) strcpy (where, argv[0]);
51181404Speter
512128266Speter	/* If mfile isn't null, we need to set up to do only part of theu
513128266Speter         * module.
514128266Speter         */
51581404Speter	if (mfile != NULL)
51681404Speter	{
51781404Speter	    char *cp;
51881404Speter	    char *path;
51981404Speter
520128266Speter	    /* If the portion of the module is a path, put the dir part on
521128266Speter             * repos.
522128266Speter             */
52381404Speter	    if ((cp = strrchr (mfile, '/')) != NULL)
52481404Speter	    {
52581404Speter		*cp = '\0';
526128266Speter		(void)strcat (repository, "/");
527128266Speter		(void)strcat (repository, mfile);
528128266Speter		(void)strcat (where, "/");
529128266Speter		(void)strcat (where, mfile);
53081404Speter		mfile = cp + 1;
53181404Speter	    }
53281404Speter
53381404Speter	    /* take care of the rest */
53481404Speter	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
535128266Speter	    (void)sprintf (path, "%s/%s", repository, mfile);
53681404Speter	    if (isdir (path))
53781404Speter	    {
53881404Speter		/* directory means repository gets the dir tacked on */
539128266Speter		(void)strcpy (repository, path);
540128266Speter		(void)strcat (where, "/");
541128266Speter		(void)strcat (where, mfile);
54281404Speter	    }
54381404Speter	    else
54481404Speter	    {
54581404Speter		myargv[0] = argv[0];
54681404Speter		myargv[1] = mfile;
54781404Speter		argc = 2;
54881404Speter		argv = myargv;
54981404Speter	    }
55081404Speter	    free (path);
55181404Speter	}
55281404Speter
55381404Speter	/* cd to the starting repository */
554128266Speter	if (CVS_CHDIR (repository) < 0)
55581404Speter	{
55681404Speter	    error (0, errno, "cannot chdir to %s", repository);
55781404Speter	    free (repository);
558128266Speter	    free (where);
559128266Speter	    return 1;
56081404Speter	}
56181404Speter	/* End section which is identical to patch_proc.  */
56281404Speter
56381404Speter	which = W_REPOS | W_ATTIC;
56481404Speter    }
56581404Speter    else
56681404Speter    {
567128266Speter        repository = NULL;
56881404Speter        where = NULL;
56981404Speter        which = W_LOCAL | W_REPOS | W_ATTIC;
57081404Speter    }
57181404Speter
57281404Speter    err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
57381404Speter			   (DIRLEAVEPROC) NULL, (void *) &log_data,
574109655Speter			   argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
575128266Speter			   where, 1, repository);
576128266Speter
577128266Speter    if (!(which & W_LOCAL)) free (repository);
578128266Speter    if (where) free (where);
579128266Speter
58081404Speter    return err;
58181404Speter}
58281404Speter
58381404Speter
584128266Speter
58525839Speter/*
58625839Speter * Parse a revision list specification.
58725839Speter */
58825839Speterstatic struct option_revlist *
58925839Speterlog_parse_revlist (argstring)
59025839Speter    const char *argstring;
59125839Speter{
59266525Speter    char *orig_copy, *copy;
59325839Speter    struct option_revlist *ret, **pr;
59425839Speter
59525839Speter    /* Unfortunately, rlog accepts -r without an argument to mean that
59625839Speter       latest revision on the default branch, so we must support that
59725839Speter       for compatibility.  */
59825839Speter    if (argstring == NULL)
59966525Speter	argstring = "";
60025839Speter
60125839Speter    ret = NULL;
60225839Speter    pr = &ret;
60325839Speter
60425839Speter    /* Copy the argument into memory so that we can change it.  We
60525839Speter       don't want to change the argument because, at least as of this
60666525Speter       writing, we will use it if we send the arguments to the server.  */
60766525Speter    orig_copy = copy = xstrdup (argstring);
60825839Speter    while (copy != NULL)
60925839Speter    {
61025839Speter	char *comma;
61125839Speter	struct option_revlist *r;
61225839Speter
61325839Speter	comma = strchr (copy, ',');
61425839Speter	if (comma != NULL)
61525839Speter	    *comma++ = '\0';
61625839Speter
61725839Speter	r = (struct option_revlist *) xmalloc (sizeof *r);
61825839Speter	r->next = NULL;
61966525Speter	r->first = copy;
62066525Speter	r->branchhead = 0;
62166525Speter	r->last = strchr (copy, ':');
62266525Speter	if (r->last != NULL)
62381404Speter	{
62466525Speter	    *r->last++ = '\0';
62581404Speter	    r->inclusive = (*r->last != ':');
62681404Speter	    if (!r->inclusive)
62781404Speter		r->last++;
62881404Speter	}
62925839Speter	else
63025839Speter	{
63166525Speter	    r->last = r->first;
63281404Speter	    r->inclusive = 1;
63366525Speter	    if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
63466525Speter	    {
63566525Speter		r->branchhead = 1;
63666525Speter		r->first[strlen (r->first) - 1] = '\0';
63766525Speter	    }
63825839Speter	}
63925839Speter
64066525Speter	if (*r->first == '\0')
64166525Speter	    r->first = NULL;
64266525Speter	if (*r->last == '\0')
64366525Speter	    r->last = NULL;
64466525Speter
64566525Speter	if (r->first != NULL)
64666525Speter	    r->first = xstrdup (r->first);
64766525Speter	if (r->last != NULL)
64866525Speter	    r->last = xstrdup (r->last);
64966525Speter
65025839Speter	*pr = r;
65125839Speter	pr = &r->next;
65225839Speter
65325839Speter	copy = comma;
65425839Speter    }
65525839Speter
65666525Speter    free (orig_copy);
65725839Speter    return ret;
65825839Speter}
65925839Speter
66017721Speter/*
66125839Speter * Parse a date specification.
66225839Speter */
66325839Speterstatic void
66425839Speterlog_parse_date (log_data, argstring)
66525839Speter    struct log_data *log_data;
66625839Speter    const char *argstring;
66725839Speter{
66825839Speter    char *orig_copy, *copy;
66925839Speter
67025839Speter    /* Copy the argument into memory so that we can change it.  We
67125839Speter       don't want to change the argument because, at least as of this
67225839Speter       writing, we will use it if we send the arguments to the server.  */
67366525Speter    orig_copy = copy = xstrdup (argstring);
67425839Speter    while (copy != NULL)
67525839Speter    {
67625839Speter	struct datelist *nd, **pd;
67725839Speter	char *cpend, *cp, *ds, *de;
67825839Speter
67925839Speter	nd = (struct datelist *) xmalloc (sizeof *nd);
68025839Speter
68125839Speter	cpend = strchr (copy, ';');
68225839Speter	if (cpend != NULL)
68325839Speter	    *cpend++ = '\0';
68425839Speter
68525839Speter	pd = &log_data->datelist;
68625839Speter	nd->inclusive = 0;
68725839Speter
68825839Speter	if ((cp = strchr (copy, '>')) != NULL)
68925839Speter	{
69025839Speter	    *cp++ = '\0';
69125839Speter	    if (*cp == '=')
69225839Speter	    {
69325839Speter		++cp;
69425839Speter		nd->inclusive = 1;
69525839Speter	    }
69625839Speter	    ds = cp;
69725839Speter	    de = copy;
69825839Speter	}
69925839Speter	else if ((cp = strchr (copy, '<')) != NULL)
70025839Speter	{
70125839Speter	    *cp++ = '\0';
70225839Speter	    if (*cp == '=')
70325839Speter	    {
70425839Speter		++cp;
70525839Speter		nd->inclusive = 1;
70625839Speter	    }
70725839Speter	    ds = copy;
70825839Speter	    de = cp;
70925839Speter	}
71025839Speter	else
71125839Speter	{
71225839Speter	    ds = NULL;
71325839Speter	    de = copy;
71425839Speter	    pd = &log_data->singledatelist;
71525839Speter	}
71625839Speter
71725839Speter	if (ds == NULL)
71825839Speter	    nd->start = NULL;
71925839Speter	else if (*ds != '\0')
72025839Speter	    nd->start = Make_Date (ds);
72125839Speter	else
72225839Speter	{
72325839Speter	  /* 1970 was the beginning of time, as far as get_date and
72425839Speter	     Make_Date are concerned.  FIXME: That is true only if time_t
72525839Speter	     is a POSIX-style time and there is nothing in ANSI that
72625839Speter	     mandates that.  It would be cleaner to set a flag saying
72725839Speter	     whether or not there is a start date.  */
72825839Speter	    nd->start = Make_Date ("1/1/1970 UTC");
72925839Speter	}
73025839Speter
73125839Speter	if (*de != '\0')
73225839Speter	    nd->end = Make_Date (de);
73325839Speter	else
73425839Speter	{
73525839Speter	    /* We want to set the end date to some time sufficiently far
73625839Speter	       in the future to pick up all revisions that have been
73725839Speter	       created since the specified date and the time `cvs log'
73825839Speter	       completes.  FIXME: The date in question only makes sense
73925839Speter	       if time_t is a POSIX-style time and it is 32 bits
74025839Speter	       and signed.  We should instead be setting a flag saying
74125839Speter	       whether or not there is an end date.  Note that using
74225839Speter	       something like "next week" would break the testsuite (and,
74325839Speter	       perhaps less importantly, loses if the clock is set grossly
74425839Speter	       wrong).  */
74525839Speter	    nd->end = Make_Date ("2038-01-01");
74625839Speter	}
74725839Speter
74825839Speter	nd->next = *pd;
74925839Speter	*pd = nd;
75025839Speter
75125839Speter	copy = cpend;
75225839Speter    }
75325839Speter
75425839Speter    free (orig_copy);
75525839Speter}
75625839Speter
75725839Speter/*
75825839Speter * Parse a comma separated list of items, and add each one to *PLIST.
75925839Speter */
76025839Speterstatic void
76125839Speterlog_parse_list (plist, argstring)
76225839Speter    List **plist;
76325839Speter    const char *argstring;
76425839Speter{
76525839Speter    while (1)
76625839Speter    {
76725839Speter	Node *p;
76825839Speter	char *cp;
76925839Speter
77025839Speter	p = getnode ();
77125839Speter
77225839Speter	cp = strchr (argstring, ',');
77325839Speter	if (cp == NULL)
77425839Speter	    p->key = xstrdup (argstring);
77525839Speter	else
77625839Speter	{
77725839Speter	    size_t len;
77825839Speter
77925839Speter	    len = cp - argstring;
78025839Speter	    p->key = xmalloc (len + 1);
78125839Speter	    strncpy (p->key, argstring, len);
782107484Speter	    p->key[len] = '\0';
78325839Speter	}
78425839Speter
78525839Speter	if (*plist == NULL)
78625839Speter	    *plist = getlist ();
78725839Speter	if (addnode (*plist, p) != 0)
78825839Speter	    freenode (p);
78925839Speter
79025839Speter	if (cp == NULL)
79125839Speter	    break;
79225839Speter
79325839Speter	argstring = cp + 1;
79425839Speter    }
79525839Speter}
79625839Speter
79732785Speterstatic int printlock_proc PROTO ((Node *, void *));
79832785Speter
79932785Speterstatic int
80032785Speterprintlock_proc (lock, foo)
80132785Speter    Node *lock;
80232785Speter    void *foo;
80332785Speter{
80432785Speter    cvs_output ("\n\t", 2);
80532785Speter    cvs_output (lock->data, 0);
80632785Speter    cvs_output (": ", 2);
80732785Speter    cvs_output (lock->key, 0);
80832785Speter    return 0;
80932785Speter}
81032785Speter
811128266Speter
812128266Speter
81325839Speter/*
81417721Speter * Do an rlog on a file
81517721Speter */
81617721Speterstatic int
81725839Speterlog_fileproc (callerdat, finfo)
81825839Speter    void *callerdat;
81917721Speter    struct file_info *finfo;
82017721Speter{
82125839Speter    struct log_data *log_data = (struct log_data *) callerdat;
82217721Speter    Node *p;
823175273Sobrien    char *baserev;
824102840Speter    int selrev = -1;
82517721Speter    RCSNode *rcsfile;
82625839Speter    char buf[50];
827128266Speter    struct revlist *revlist = NULL;
82825839Speter    struct log_data_and_rcs log_data_and_rcs;
82917721Speter
830175273Sobrien    rcsfile = finfo->rcs;
831175273Sobrien    p = findnode (finfo->entries, finfo->file);
832175273Sobrien    if (p != NULL)
83317721Speter    {
834175273Sobrien	Entnode *e = p->data;
835175273Sobrien	baserev = e->version;
836175273Sobrien	if (baserev[0] == '-') ++baserev;
837175273Sobrien    }
838175273Sobrien    else
839175273Sobrien	baserev = NULL;
840175273Sobrien
841175273Sobrien    if (rcsfile == NULL)
842175273Sobrien    {
84317721Speter	/* no rcs file.  What *do* we know about this file? */
844175273Sobrien	if (baserev != NULL)
84517721Speter	{
846175273Sobrien	    if (baserev[0] == '0' && baserev[1] == '\0')
84717721Speter	    {
84817721Speter		if (!really_quiet)
84917721Speter		    error (0, 0, "%s has been added, but not committed",
85017721Speter			   finfo->file);
851128266Speter		return 0;
85217721Speter	    }
85317721Speter	}
85417721Speter
85517721Speter	if (!really_quiet)
85617721Speter	    error (0, 0, "nothing known about %s", finfo->file);
85717721Speter
858128266Speter	return 1;
85917721Speter    }
86017721Speter
861102840Speter    if (log_data->sup_header || !log_data->nameonly)
862102840Speter    {
863102840Speter
864102840Speter	/* We will need all the information in the RCS file.  */
865102840Speter	RCS_fully_parse (rcsfile);
866102840Speter
867102840Speter	/* Turn any symbolic revisions in the revision list into numeric
868102840Speter	   revisions.  */
869175273Sobrien	revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist,
870102840Speter				      log_data->default_branch);
871128266Speter	if (log_data->sup_header
872128266Speter            || (!log_data->header && !log_data->long_header))
873102840Speter	{
874102840Speter	    log_data_and_rcs.log_data = log_data;
875102840Speter	    log_data_and_rcs.revlist = revlist;
876102840Speter	    log_data_and_rcs.rcs = rcsfile;
877102840Speter
878102840Speter	    /* If any single dates were specified, we need to identify the
879102840Speter	       revisions they select.  Each one selects the single
880102840Speter	       revision, which is otherwise selected, of that date or
881102840Speter	       earlier.  The log_fix_singledate routine will fill in the
882102840Speter	       start date for each specific revision.  */
883102840Speter	    if (log_data->singledatelist != NULL)
884102840Speter		walklist (rcsfile->versions, log_fix_singledate,
885128266Speter			  (void *)&log_data_and_rcs);
886102840Speter
887102840Speter	    selrev = walklist (rcsfile->versions, log_count_print,
888128266Speter			       (void *)&log_data_and_rcs);
889128266Speter	    if (log_data->sup_header && selrev == 0)
890128266Speter	    {
891128266Speter		log_free_revlist (revlist);
892128266Speter		return 0;
893128266Speter	    }
894102840Speter	}
895102840Speter
896102840Speter    }
897102840Speter
89825839Speter    if (log_data->nameonly)
89917721Speter    {
90025839Speter	cvs_output (rcsfile->path, 0);
90125839Speter	cvs_output ("\n", 1);
902128266Speter	log_free_revlist (revlist);
90325839Speter	return 0;
90417721Speter    }
90517721Speter
90625839Speter    /* The output here is intended to be exactly compatible with the
90725839Speter       output of rlog.  I'm not sure whether this code should be here
90825839Speter       or in rcs.c; I put it here because it is specific to the log
90925839Speter       function, even though it uses information gathered by the
91025839Speter       functions in rcs.c.  */
91125839Speter
91225839Speter    cvs_output ("\n", 1);
91325839Speter
91425839Speter    cvs_output ("RCS file: ", 0);
91525839Speter    cvs_output (rcsfile->path, 0);
91625839Speter
91781404Speter    if (!is_rlog)
91817721Speter    {
91981404Speter	cvs_output ("\nWorking file: ", 0);
92081404Speter	if (finfo->update_dir[0] != '\0')
92181404Speter	{
92281404Speter	    cvs_output (finfo->update_dir, 0);
92381404Speter	    cvs_output ("/", 0);
92481404Speter	}
92525839Speter	cvs_output (finfo->file, 0);
92617721Speter    }
92717721Speter
92825839Speter    cvs_output ("\nhead:", 0);
92925839Speter    if (rcsfile->head != NULL)
93017721Speter    {
93125839Speter	cvs_output (" ", 1);
93225839Speter	cvs_output (rcsfile->head, 0);
93317721Speter    }
93425839Speter
93525839Speter    cvs_output ("\nbranch:", 0);
93625839Speter    if (rcsfile->branch != NULL)
93725839Speter    {
93825839Speter	cvs_output (" ", 1);
93925839Speter	cvs_output (rcsfile->branch, 0);
94025839Speter    }
94125839Speter
94225839Speter    cvs_output ("\nlocks:", 0);
94332785Speter    if (rcsfile->strict_locks)
94432785Speter	cvs_output (" strict", 0);
94532785Speter    walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
94625839Speter
94725839Speter    cvs_output ("\naccess list:", 0);
94832785Speter    if (rcsfile->access != NULL)
94925839Speter    {
95032785Speter	const char *cp;
95132785Speter
95232785Speter	cp = rcsfile->access;
95332785Speter	while (*cp != '\0')
95425839Speter	{
95525839Speter		const char *cp2;
95625839Speter
95725839Speter		cvs_output ("\n\t", 2);
95825839Speter		cp2 = cp;
959128266Speter		while (!isspace ((unsigned char) *cp2) && *cp2 != '\0')
96025839Speter		    ++cp2;
96125839Speter		cvs_output (cp, cp2 - cp);
96225839Speter		cp = cp2;
96354427Speter		while (isspace ((unsigned char) *cp) && *cp != '\0')
96425839Speter		    ++cp;
96525839Speter	}
96625839Speter    }
96725839Speter
968128266Speter    if (!log_data->notags)
96925839Speter    {
97025839Speter	List *syms;
97125839Speter
97225839Speter	cvs_output ("\nsymbolic names:", 0);
97325839Speter	syms = RCS_symbols (rcsfile);
97425839Speter	walklist (syms, log_symbol, NULL);
97525839Speter    }
97625839Speter
97725839Speter    cvs_output ("\nkeyword substitution: ", 0);
97825839Speter    if (rcsfile->expand == NULL)
97925839Speter	cvs_output ("kv", 2);
98025839Speter    else
98125839Speter	cvs_output (rcsfile->expand, 0);
98225839Speter
98325839Speter    cvs_output ("\ntotal revisions: ", 0);
98425839Speter    sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
98525839Speter    cvs_output (buf, 0);
98625839Speter
987102840Speter    if (selrev >= 0)
98825839Speter    {
98925839Speter	cvs_output (";\tselected revisions: ", 0);
990102840Speter	sprintf (buf, "%d", selrev);
99125839Speter	cvs_output (buf, 0);
99225839Speter    }
99325839Speter
99425839Speter    cvs_output ("\n", 1);
99525839Speter
996128266Speter    if (!log_data->header || log_data->long_header)
99725839Speter    {
99825839Speter	cvs_output ("description:\n", 0);
99932785Speter	if (rcsfile->desc != NULL)
100032785Speter	    cvs_output (rcsfile->desc, 0);
100125839Speter    }
100225839Speter
1003128266Speter    if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
100425839Speter    {
100525839Speter	p = findnode (rcsfile->versions, rcsfile->head);
100625839Speter	if (p == NULL)
100725839Speter	    error (1, 0, "can not find head revision in `%s'",
100825839Speter		   finfo->fullname);
100925839Speter	while (p != NULL)
101025839Speter	{
1011128266Speter	    RCSVers *vers = p->data;
101225839Speter
101325839Speter	    log_version (log_data, revlist, rcsfile, vers, 1);
101425839Speter	    if (vers->next == NULL)
101525839Speter		p = NULL;
101625839Speter	    else
101725839Speter	    {
101825839Speter		p = findnode (rcsfile->versions, vers->next);
101925839Speter		if (p == NULL)
102025839Speter		    error (1, 0, "can not find next revision `%s' in `%s'",
102125839Speter			   vers->next, finfo->fullname);
102225839Speter	    }
102325839Speter	}
102425839Speter
102525839Speter	log_tree (log_data, revlist, rcsfile, rcsfile->head);
102625839Speter    }
102725839Speter
102825839Speter    cvs_output("\
102925839Speter=============================================================================\n",
103025839Speter	       0);
103125839Speter
103225839Speter    /* Free up the new revlist and restore the old one.  */
103325839Speter    log_free_revlist (revlist);
103425839Speter
103525839Speter    /* If singledatelist is not NULL, free up the start dates we added
103625839Speter       to it.  */
103725839Speter    if (log_data->singledatelist != NULL)
103825839Speter    {
103925839Speter	struct datelist *d;
104025839Speter
104125839Speter	for (d = log_data->singledatelist; d != NULL; d = d->next)
104225839Speter	{
104325839Speter	    if (d->start != NULL)
104425839Speter		free (d->start);
104525839Speter	    d->start = NULL;
104625839Speter	}
104725839Speter    }
104825839Speter
104925839Speter    return 0;
105017721Speter}
105117721Speter
1052128266Speter
1053128266Speter
105417721Speter/*
105525839Speter * Fix up a revision list in order to compare it against versions.
105625839Speter * Expand any symbolic revisions.
105725839Speter */
105825839Speterstatic struct revlist *
1059175273Sobrienlog_expand_revlist (rcs, baserev, revlist, default_branch)
106025839Speter    RCSNode *rcs;
1061175273Sobrien    char *baserev;
106225839Speter    struct option_revlist *revlist;
106325839Speter    int default_branch;
106425839Speter{
106525839Speter    struct option_revlist *r;
106625839Speter    struct revlist *ret, **pr;
106725839Speter
106825839Speter    ret = NULL;
106925839Speter    pr = &ret;
107025839Speter    for (r = revlist; r != NULL; r = r->next)
107125839Speter    {
107225839Speter	struct revlist *nr;
107325839Speter
107425839Speter	nr = (struct revlist *) xmalloc (sizeof *nr);
107581404Speter	nr->inclusive = r->inclusive;
107625839Speter
107725839Speter	if (r->first == NULL && r->last == NULL)
107825839Speter	{
107925839Speter	    /* If both first and last are NULL, it means that we want
108025839Speter	       just the head of the default branch, which is RCS_head.  */
108125839Speter	    nr->first = RCS_head (rcs);
1082175273Sobrien	    if (!nr->first)
1083175273Sobrien	    {
1084175273Sobrien		if (!really_quiet)
1085175273Sobrien		    error (0, 0, "No head revision in archive `%s'.",
1086175273Sobrien		           rcs->path);
1087175273Sobrien		nr->last = NULL;
1088175273Sobrien		nr->fields = 0;
1089175273Sobrien	    }
1090175273Sobrien	    else
1091175273Sobrien	    {
1092175273Sobrien		nr->last = xstrdup (nr->first);
1093175273Sobrien		nr->fields = numdots (nr->first) + 1;
1094175273Sobrien	    }
109525839Speter	}
109625839Speter	else if (r->branchhead)
109725839Speter	{
109825839Speter	    char *branch;
109925839Speter
1100175273Sobrien	    assert (r->first != NULL);
1101175273Sobrien
110225839Speter	    /* Print just the head of the branch.  */
110354427Speter	    if (isdigit ((unsigned char) r->first[0]))
110425839Speter		nr->first = RCS_getbranch (rcs, r->first, 1);
110525839Speter	    else
110625839Speter	    {
110725839Speter		branch = RCS_whatbranch (rcs, r->first);
110825839Speter		if (branch == NULL)
1109102840Speter		    nr->first = NULL;
1110102840Speter		else
111125839Speter		{
1112102840Speter		    nr->first = RCS_getbranch (rcs, branch, 1);
1113102840Speter		    free (branch);
111425839Speter		}
111525839Speter	    }
1116175273Sobrien	    if (!nr->first)
111725839Speter	    {
1118175273Sobrien		if (!really_quiet)
1119175273Sobrien		    error (0, 0, "warning: no branch `%s' in `%s'",
1120175273Sobrien			   r->first, rcs->path);
1121102840Speter		nr->last = NULL;
1122102840Speter		nr->fields = 0;
112325839Speter	    }
1124102840Speter	    else
1125102840Speter	    {
1126102840Speter		nr->last = xstrdup (nr->first);
1127102840Speter		nr->fields = numdots (nr->first) + 1;
1128102840Speter	    }
112925839Speter	}
113025839Speter	else
113125839Speter	{
113254427Speter	    if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
113325839Speter		nr->first = xstrdup (r->first);
113425839Speter	    else
113525839Speter	    {
1136175273Sobrien		if (baserev && strcmp (r->first, TAG_BASE) == 0)
1137175273Sobrien		    nr->first = xstrdup (baserev);
1138175273Sobrien		else if (RCS_nodeisbranch (rcs, r->first))
113925839Speter		    nr->first = RCS_whatbranch (rcs, r->first);
114025839Speter		else
114125839Speter		    nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
1142130303Speter		if (nr->first == NULL && !really_quiet)
114325839Speter		{
114425839Speter		    error (0, 0, "warning: no revision `%s' in `%s'",
114525839Speter			   r->first, rcs->path);
114625839Speter		}
114725839Speter	    }
114825839Speter
1149102840Speter	    if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1150102840Speter					strcmp (r->last, r->first) == 0))
115125839Speter		nr->last = xstrdup (nr->first);
115254427Speter	    else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
115325839Speter		nr->last = xstrdup (r->last);
115425839Speter	    else
115525839Speter	    {
1156175273Sobrien		if (baserev && strcmp (r->last, TAG_BASE) == 0)
1157175273Sobrien		    nr->last = xstrdup (baserev);
1158175273Sobrien		else if (RCS_nodeisbranch (rcs, r->last))
115925839Speter		    nr->last = RCS_whatbranch (rcs, r->last);
116025839Speter		else
116125839Speter		    nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
1162130303Speter		if (nr->last == NULL && !really_quiet)
116325839Speter		{
116425839Speter		    error (0, 0, "warning: no revision `%s' in `%s'",
116525839Speter			   r->last, rcs->path);
116625839Speter		}
116725839Speter	    }
116825839Speter
116925839Speter	    /* Process the revision numbers the same way that rlog
117025839Speter               does.  This code is a bit cryptic for my tastes, but
117125839Speter               keeping the same implementation as rlog ensures a
117225839Speter               certain degree of compatibility.  */
1173128266Speter	    if (r->first == NULL && nr->last != NULL)
117425839Speter	    {
1175128266Speter		nr->fields = numdots (nr->last) + 1;
1176128266Speter		if (nr->fields < 2)
1177128266Speter		    nr->first = xstrdup (".0");
117825839Speter		else
117925839Speter		{
1180128266Speter		    char *cp;
118125839Speter
1182128266Speter		    nr->first = xstrdup (nr->last);
1183128266Speter		    cp = strrchr (nr->first, '.');
1184175273Sobrien		    assert (cp);
1185128266Speter		    strcpy (cp + 1, "0");
118625839Speter		}
118725839Speter	    }
1188128266Speter	    else if (r->last == NULL && nr->first != NULL)
118925839Speter	    {
119025839Speter		nr->fields = numdots (nr->first) + 1;
119125839Speter		nr->last = xstrdup (nr->first);
119225839Speter		if (nr->fields < 2)
119325839Speter		    nr->last[0] = '\0';
119425839Speter		else
119525839Speter		{
119625839Speter		    char *cp;
119725839Speter
119825839Speter		    cp = strrchr (nr->last, '.');
1199175273Sobrien		    assert (cp);
120025839Speter		    *cp = '\0';
120125839Speter		}
120225839Speter	    }
1203107484Speter	    else if (nr->first == NULL || nr->last == NULL)
1204107484Speter		nr->fields = 0;
1205107484Speter	    else if (strcmp (nr->first, nr->last) == 0)
1206107484Speter		nr->fields = numdots (nr->last) + 1;
1207107484Speter	    else
120825839Speter	    {
1209107484Speter		int ord;
1210107484Speter		int dots1 = numdots (nr->first);
1211107484Speter		int dots2 = numdots (nr->last);
1212107484Speter		if (dots1 > dots2 || (dots1 == dots2 &&
1213107484Speter		    version_compare (nr->first, nr->last, dots1 + 1) > 0))
121425839Speter		{
1215107484Speter		    char *tmp = nr->first;
1216107484Speter		    nr->first = nr->last;
1217107484Speter		    nr->last = tmp;
1218107484Speter		    nr->fields = dots2 + 1;
1219107484Speter		    dots2 = dots1;
1220107484Speter		    dots1 = nr->fields - 1;
1221107484Speter		}
1222107484Speter		else
1223107484Speter		    nr->fields = dots1 + 1;
1224107484Speter		dots1 += (nr->fields & 1);
1225107484Speter		ord = version_compare (nr->first, nr->last, dots1);
1226107484Speter		if (ord > 0 || (nr->fields > 2 && ord < 0))
1227107484Speter		{
122825839Speter		    error (0, 0,
122925839Speter			   "invalid branch or revision pair %s:%s in `%s'",
123025839Speter			   r->first, r->last, rcs->path);
123125839Speter		    free (nr->first);
1232102840Speter		    nr->first = NULL;
123325839Speter		    free (nr->last);
1234102840Speter		    nr->last = NULL;
1235102840Speter		    nr->fields = 0;
123625839Speter		}
1237107484Speter		else
123825839Speter		{
1239107484Speter		    if (nr->fields <= dots2 && (nr->fields & 1))
1240107484Speter		    {
1241107484Speter			char *p = xmalloc (strlen (nr->first) + 3);
1242107484Speter			strcpy (p, nr->first);
1243107484Speter			strcat (p, ".0");
1244107484Speter			free (nr->first);
1245107484Speter			nr->first = p;
1246107484Speter			++nr->fields;
1247107484Speter		    }
1248107484Speter		    while (nr->fields <= dots2)
1249107484Speter		    {
1250107484Speter			char *p;
1251107484Speter			int i;
125225839Speter
1253107484Speter			nr->next = NULL;
1254107484Speter			*pr = nr;
1255107484Speter			nr = (struct revlist *) xmalloc (sizeof *nr);
1256107484Speter			nr->inclusive = 1;
1257107484Speter			nr->first = xstrdup ((*pr)->last);
1258107484Speter			nr->last = xstrdup ((*pr)->last);
1259107484Speter			nr->fields = (*pr)->fields;
1260107484Speter			p = (*pr)->last;
1261107484Speter			for (i = 0; i < nr->fields; i++)
1262107484Speter			    p = strchr (p, '.') + 1;
1263107484Speter			p[-1] = '\0';
1264107484Speter			p = strchr (nr->first + (p - (*pr)->last), '.');
1265107484Speter			if (p != NULL)
1266107484Speter			{
1267107484Speter			    *++p = '0';
1268107484Speter			    *++p = '\0';
1269107484Speter			    nr->fields += 2;
1270107484Speter			}
1271107484Speter			else
1272107484Speter			    ++nr->fields;
1273107484Speter			pr = &(*pr)->next;
1274107484Speter		    }
127525839Speter		}
127625839Speter	    }
127725839Speter	}
127825839Speter
127925839Speter	nr->next = NULL;
128025839Speter	*pr = nr;
128125839Speter	pr = &nr->next;
128225839Speter    }
128325839Speter
128425839Speter    /* If the default branch was requested, add a revlist entry for
128525839Speter       it.  This is how rlog handles this option.  */
128625839Speter    if (default_branch
128725839Speter	&& (rcs->head != NULL || rcs->branch != NULL))
128825839Speter    {
128925839Speter	struct revlist *nr;
129025839Speter
129125839Speter	nr = (struct revlist *) xmalloc (sizeof *nr);
129225839Speter	if (rcs->branch != NULL)
129325839Speter	    nr->first = xstrdup (rcs->branch);
129425839Speter	else
129525839Speter	{
129625839Speter	    char *cp;
129725839Speter
129825839Speter	    nr->first = xstrdup (rcs->head);
1299175273Sobrien	    assert (nr->first);
130025839Speter	    cp = strrchr (nr->first, '.');
1301175273Sobrien	    assert (cp);
130225839Speter	    *cp = '\0';
130325839Speter	}
130425839Speter	nr->last = xstrdup (nr->first);
130525839Speter	nr->fields = numdots (nr->first) + 1;
130681404Speter	nr->inclusive = 1;
130725839Speter
130825839Speter	nr->next = NULL;
130925839Speter	*pr = nr;
131025839Speter    }
131125839Speter
131225839Speter    return ret;
131325839Speter}
131425839Speter
131525839Speter/*
131625839Speter * Free a revlist created by log_expand_revlist.
131725839Speter */
131825839Speterstatic void
131925839Speterlog_free_revlist (revlist)
132025839Speter    struct revlist *revlist;
132125839Speter{
132225839Speter    struct revlist *r;
132325839Speter
132425839Speter    r = revlist;
132525839Speter    while (r != NULL)
132625839Speter    {
132725839Speter	struct revlist *next;
132825839Speter
132925839Speter	if (r->first != NULL)
133025839Speter	    free (r->first);
133125839Speter	if (r->last != NULL)
133225839Speter	    free (r->last);
133325839Speter	next = r->next;
133425839Speter	free (r);
133525839Speter	r = next;
133625839Speter    }
133725839Speter}
133825839Speter
133925839Speter/*
134025839Speter * Return nonzero if a revision should be printed, based on the
134125839Speter * options provided.
134225839Speter */
134325839Speterstatic int
134425839Speterlog_version_requested (log_data, revlist, rcs, vnode)
134525839Speter    struct log_data *log_data;
134625839Speter    struct revlist *revlist;
134725839Speter    RCSNode *rcs;
134825839Speter    RCSVers *vnode;
134925839Speter{
135025839Speter    /* Handle the list of states from the -s option.  */
135125839Speter    if (log_data->statelist != NULL
135225839Speter	&& findnode (log_data->statelist, vnode->state) == NULL)
135325839Speter    {
135425839Speter	return 0;
135525839Speter    }
135625839Speter
135725839Speter    /* Handle the list of authors from the -w option.  */
135825839Speter    if (log_data->authorlist != NULL)
135925839Speter    {
136025839Speter	if (vnode->author != NULL
136125839Speter	    && findnode (log_data->authorlist, vnode->author) == NULL)
136225839Speter	{
136325839Speter	    return 0;
136425839Speter	}
136525839Speter    }
136625839Speter
136725839Speter    /* rlog considers all the -d options together when it decides
136825839Speter       whether to print a revision, so we must be compatible.  */
136925839Speter    if (log_data->datelist != NULL || log_data->singledatelist != NULL)
137025839Speter    {
137125839Speter	struct datelist *d;
137225839Speter
137325839Speter	for (d = log_data->datelist; d != NULL; d = d->next)
137425839Speter	{
137525839Speter	    int cmp;
137625839Speter
137725839Speter	    cmp = RCS_datecmp (vnode->date, d->start);
137825839Speter	    if (cmp > 0 || (cmp == 0 && d->inclusive))
137925839Speter	    {
138025839Speter		cmp = RCS_datecmp (vnode->date, d->end);
138125839Speter		if (cmp < 0 || (cmp == 0 && d->inclusive))
138225839Speter		    break;
138325839Speter	    }
138425839Speter	}
138525839Speter
138625839Speter	if (d == NULL)
138725839Speter	{
138825839Speter	    /* Look through the list of specific dates.  We want to
138925839Speter	       select the revision with the exact date found in the
139025839Speter	       start field.  The commit code ensures that it is
139125839Speter	       impossible to check in multiple revisions of a single
139225839Speter	       file in a single second, so checking the date this way
139325839Speter	       should never select more than one revision.  */
139425839Speter	    for (d = log_data->singledatelist; d != NULL; d = d->next)
139525839Speter	    {
139625839Speter		if (d->start != NULL
139725839Speter		    && RCS_datecmp (vnode->date, d->start) == 0)
139825839Speter		{
139925839Speter		    break;
140025839Speter		}
140125839Speter	    }
140225839Speter
140325839Speter	    if (d == NULL)
140425839Speter		return 0;
140525839Speter	}
140625839Speter    }
140725839Speter
140825839Speter    /* If the -r or -b options were used, REVLIST will be non NULL,
140925839Speter       and we print the union of the specified revisions.  */
141025839Speter    if (revlist != NULL)
141125839Speter    {
141225839Speter	char *v;
141325839Speter	int vfields;
141425839Speter	struct revlist *r;
141525839Speter
141625839Speter	/* This code is taken from rlog.  */
141725839Speter	v = vnode->version;
141825839Speter	vfields = numdots (v) + 1;
141925839Speter	for (r = revlist; r != NULL; r = r->next)
142025839Speter	{
142181404Speter	    if (vfields == r->fields + (r->fields & 1) &&
1422102840Speter		(r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1423102840Speter				version_compare (v, r->first, r->fields) > 0)
1424102840Speter		    && version_compare (v, r->last, r->fields) <= 0)
142525839Speter	    {
142625839Speter		return 1;
142725839Speter	    }
142825839Speter	}
142925839Speter
143025839Speter	/* If we get here, then the -b and/or the -r option was used,
143125839Speter           but did not match this revision, so we reject it.  */
143225839Speter
143325839Speter	return 0;
143425839Speter    }
143525839Speter
143625839Speter    /* By default, we print all revisions.  */
143725839Speter    return 1;
143825839Speter}
143925839Speter
1440128266Speter
1441128266Speter
144225839Speter/*
144325839Speter * Output a single symbol.  This is called via walklist.
144425839Speter */
144525839Speter/*ARGSUSED*/
144625839Speterstatic int
144725839Speterlog_symbol (p, closure)
144825839Speter    Node *p;
144925839Speter    void *closure;
145025839Speter{
145125839Speter    cvs_output ("\n\t", 2);
145225839Speter    cvs_output (p->key, 0);
145325839Speter    cvs_output (": ", 2);
145425839Speter    cvs_output (p->data, 0);
145525839Speter    return 0;
145625839Speter}
145725839Speter
1458128266Speter
1459128266Speter
146025839Speter/*
146125839Speter * Count the number of entries on a list.  This is called via walklist.
146225839Speter */
146325839Speter/*ARGSUSED*/
146425839Speterstatic int
146525839Speterlog_count (p, closure)
146625839Speter    Node *p;
146725839Speter    void *closure;
146825839Speter{
146925839Speter    return 1;
147025839Speter}
147125839Speter
1472128266Speter
1473128266Speter
147425839Speter/*
147525839Speter * Sort out a single date specification by narrowing down the date
147625839Speter * until we find the specific selected revision.
147725839Speter */
147825839Speterstatic int
147925839Speterlog_fix_singledate (p, closure)
148025839Speter    Node *p;
148125839Speter    void *closure;
148225839Speter{
148325839Speter    struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
148425839Speter    Node *pv;
148525839Speter    RCSVers *vnode;
148625839Speter    struct datelist *holdsingle, *holddate;
148725839Speter    int requested;
148825839Speter
148925839Speter    pv = findnode (data->rcs->versions, p->key);
149025839Speter    if (pv == NULL)
149125839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
149225839Speter	       p->key, data->rcs->path);
1493128266Speter    vnode = pv->data;
149425839Speter
149525839Speter    /* We are only interested if this revision passes any other tests.
149625839Speter       Temporarily clear log_data->singledatelist to avoid confusing
149725839Speter       log_version_requested.  We also clear log_data->datelist,
149825839Speter       because rlog considers all the -d options together.  We don't
149925839Speter       want to reject a revision because it does not match a date pair
150025839Speter       if we are going to select it on the basis of the singledate.  */
150125839Speter    holdsingle = data->log_data->singledatelist;
150225839Speter    data->log_data->singledatelist = NULL;
150325839Speter    holddate = data->log_data->datelist;
150425839Speter    data->log_data->datelist = NULL;
150525839Speter    requested = log_version_requested (data->log_data, data->revlist,
150625839Speter				       data->rcs, vnode);
150725839Speter    data->log_data->singledatelist = holdsingle;
150825839Speter    data->log_data->datelist = holddate;
150925839Speter
151025839Speter    if (requested)
151125839Speter    {
151225839Speter	struct datelist *d;
151325839Speter
151425839Speter	/* For each single date, if this revision is before the
151525839Speter	   specified date, but is closer than the previously selected
151625839Speter	   revision, select it instead.  */
151725839Speter	for (d = data->log_data->singledatelist; d != NULL; d = d->next)
151825839Speter	{
151925839Speter	    if (RCS_datecmp (vnode->date, d->end) <= 0
152025839Speter		&& (d->start == NULL
152125839Speter		    || RCS_datecmp (vnode->date, d->start) > 0))
152225839Speter	    {
152325839Speter		if (d->start != NULL)
152425839Speter		    free (d->start);
152525839Speter		d->start = xstrdup (vnode->date);
152625839Speter	    }
152725839Speter	}
152825839Speter    }
152925839Speter
153025839Speter    return 0;
153125839Speter}
153225839Speter
1533128266Speter
1534128266Speter
153525839Speter/*
153625839Speter * Count the number of revisions we are going to print.
153725839Speter */
153825839Speterstatic int
153925839Speterlog_count_print (p, closure)
154025839Speter    Node *p;
154125839Speter    void *closure;
154225839Speter{
154325839Speter    struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
154425839Speter    Node *pv;
154525839Speter
154625839Speter    pv = findnode (data->rcs->versions, p->key);
154725839Speter    if (pv == NULL)
154825839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
154925839Speter	       p->key, data->rcs->path);
155025839Speter    if (log_version_requested (data->log_data, data->revlist, data->rcs,
1551128266Speter			       pv->data))
155225839Speter	return 1;
155325839Speter    else
155425839Speter	return 0;
155525839Speter}
155625839Speter
155725839Speter/*
155825839Speter * Print the list of changes, not including the trunk, in reverse
155925839Speter * order for each branch.
156025839Speter */
156125839Speterstatic void
156225839Speterlog_tree (log_data, revlist, rcs, ver)
156325839Speter    struct log_data *log_data;
156425839Speter    struct revlist *revlist;
156525839Speter    RCSNode *rcs;
156625839Speter    const char *ver;
156725839Speter{
156825839Speter    Node *p;
156925839Speter    RCSVers *vnode;
157025839Speter
157125839Speter    p = findnode (rcs->versions, ver);
157225839Speter    if (p == NULL)
157325839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
157425839Speter	       ver, rcs->path);
1575128266Speter    vnode = p->data;
157625839Speter    if (vnode->next != NULL)
157725839Speter	log_tree (log_data, revlist, rcs, vnode->next);
157825839Speter    if (vnode->branches != NULL)
157925839Speter    {
158025839Speter	Node *head, *branch;
158125839Speter
158225839Speter	/* We need to do the branches in reverse order.  This breaks
158325839Speter           the List abstraction, but so does most of the branch
158425839Speter           manipulation in rcs.c.  */
158525839Speter	head = vnode->branches->list;
158625839Speter	for (branch = head->prev; branch != head; branch = branch->prev)
158725839Speter	{
158825839Speter	    log_abranch (log_data, revlist, rcs, branch->key);
158925839Speter	    log_tree (log_data, revlist, rcs, branch->key);
159025839Speter	}
159125839Speter    }
159225839Speter}
159325839Speter
159425839Speter/*
159525839Speter * Log the changes for a branch, in reverse order.
159625839Speter */
159725839Speterstatic void
159825839Speterlog_abranch (log_data, revlist, rcs, ver)
159925839Speter    struct log_data *log_data;
160025839Speter    struct revlist *revlist;
160125839Speter    RCSNode *rcs;
160225839Speter    const char *ver;
160325839Speter{
160425839Speter    Node *p;
160525839Speter    RCSVers *vnode;
160625839Speter
160725839Speter    p = findnode (rcs->versions, ver);
160825839Speter    if (p == NULL)
160925839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
161025839Speter	       ver, rcs->path);
1611128266Speter    vnode = p->data;
161225839Speter    if (vnode->next != NULL)
161325839Speter	log_abranch (log_data, revlist, rcs, vnode->next);
161425839Speter    log_version (log_data, revlist, rcs, vnode, 0);
161525839Speter}
161625839Speter
161725839Speter/*
161825839Speter * Print the log output for a single version.
161925839Speter */
162025839Speterstatic void
162125839Speterlog_version (log_data, revlist, rcs, ver, trunk)
162225839Speter    struct log_data *log_data;
162325839Speter    struct revlist *revlist;
162425839Speter    RCSNode *rcs;
162525839Speter    RCSVers *ver;
162625839Speter    int trunk;
162725839Speter{
162825839Speter    Node *p;
162925839Speter    int year, mon, mday, hour, min, sec;
163025839Speter    char buf[100];
163125839Speter    Node *padd, *pdel;
163225839Speter
163325839Speter    if (! log_version_requested (log_data, revlist, rcs, ver))
163425839Speter	return;
163525839Speter
163625839Speter    cvs_output ("----------------------------\nrevision ", 0);
163725839Speter    cvs_output (ver->version, 0);
163825839Speter
163932785Speter    p = findnode (RCS_getlocks (rcs), ver->version);
164025839Speter    if (p != NULL)
164125839Speter    {
164225839Speter	cvs_output ("\tlocked by: ", 0);
164325839Speter	cvs_output (p->data, 0);
164425839Speter	cvs_output (";", 1);
164525839Speter    }
164625839Speter
164725839Speter    cvs_output ("\ndate: ", 0);
164825839Speter    (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
164925839Speter		   &sec);
165025839Speter    if (year < 1900)
165125839Speter	year += 1900;
1652131688Sdes    sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d",
1653131688Sdes	     year, datesep, mon, datesep, mday, hour, min, sec);
165425839Speter    cvs_output (buf, 0);
165525839Speter
165625839Speter    cvs_output (";  author: ", 0);
165725839Speter    cvs_output (ver->author, 0);
165825839Speter
165925839Speter    cvs_output (";  state: ", 0);
166025839Speter    cvs_output (ver->state, 0);
166125839Speter    cvs_output (";", 1);
166225839Speter
166325839Speter    if (! trunk)
166425839Speter    {
166525839Speter	padd = findnode (ver->other, ";add");
166625839Speter	pdel = findnode (ver->other, ";delete");
166725839Speter    }
166825839Speter    else if (ver->next == NULL)
166925839Speter    {
167025839Speter	padd = NULL;
167125839Speter	pdel = NULL;
167225839Speter    }
167325839Speter    else
167425839Speter    {
167525839Speter	Node *nextp;
167625839Speter	RCSVers *nextver;
167725839Speter
167825839Speter	nextp = findnode (rcs->versions, ver->next);
167925839Speter	if (nextp == NULL)
168025839Speter	    error (1, 0, "missing version `%s' in `%s'", ver->next,
168125839Speter		   rcs->path);
1682128266Speter	nextver = nextp->data;
168325839Speter	pdel = findnode (nextver->other, ";add");
168425839Speter	padd = findnode (nextver->other, ";delete");
168525839Speter    }
168625839Speter
168725839Speter    if (padd != NULL)
168825839Speter    {
1689175273Sobrien	assert (pdel);
169025839Speter	cvs_output ("  lines: +", 0);
169125839Speter	cvs_output (padd->data, 0);
169225839Speter	cvs_output (" -", 2);
169325839Speter	cvs_output (pdel->data, 0);
169425839Speter    }
169525839Speter
169625839Speter    if (ver->branches != NULL)
169725839Speter    {
169825839Speter	cvs_output ("\nbranches:", 0);
169925839Speter	walklist (ver->branches, log_branch, (void *) NULL);
170025839Speter    }
170125839Speter
170225839Speter    cvs_output ("\n", 1);
170325839Speter
170425839Speter    p = findnode (ver->other, "log");
170532785Speter    /* The p->date == NULL case is the normal one for an empty log
170632785Speter       message (rcs-14 in sanity.sh).  I don't think the case where
170732785Speter       p->data is "" can happen (getrcskey in rcs.c checks for an
170832785Speter       empty string and set the value to NULL in that case).  My guess
170932785Speter       would be the p == NULL case would mean an RCS file which was
171032785Speter       missing the "log" keyword (which is illegal according to
171132785Speter       rcsfile.5).  */
1712128266Speter    if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
171325839Speter	cvs_output ("*** empty log message ***\n", 0);
171425839Speter    else
171525839Speter    {
171625839Speter	/* FIXME: Technically, the log message could contain a null
171725839Speter           byte.  */
171825839Speter	cvs_output (p->data, 0);
1719128266Speter	if (((char *)p->data)[strlen (p->data) - 1] != '\n')
172025839Speter	    cvs_output ("\n", 1);
172125839Speter    }
172225839Speter}
172325839Speter
172425839Speter/*
172525839Speter * Output a branch version.  This is called via walklist.
172625839Speter */
172725839Speter/*ARGSUSED*/
172825839Speterstatic int
172925839Speterlog_branch (p, closure)
173025839Speter    Node *p;
173125839Speter    void *closure;
173225839Speter{
173325839Speter    cvs_output ("  ", 2);
173425839Speter    if ((numdots (p->key) & 1) == 0)
173525839Speter	cvs_output (p->key, 0);
173625839Speter    else
173725839Speter    {
173825839Speter	char *f, *cp;
173925839Speter
174025839Speter	f = xstrdup (p->key);
174125839Speter	cp = strrchr (f, '.');
174225839Speter	*cp = '\0';
174325839Speter	cvs_output (f, 0);
174425839Speter	free (f);
174525839Speter    }
174625839Speter    cvs_output (";", 1);
174725839Speter    return 0;
174825839Speter}
174925839Speter
175025839Speter/*
175117721Speter * Print a warm fuzzy message
175217721Speter */
175317721Speter/* ARGSUSED */
175417721Speterstatic Dtype
175525839Speterlog_dirproc (callerdat, dir, repository, update_dir, entries)
175625839Speter    void *callerdat;
1757128266Speter    const char *dir;
1758128266Speter    const char *repository;
1759128266Speter    const char *update_dir;
176025839Speter    List *entries;
176117721Speter{
176217721Speter    if (!isdir (dir))
176317721Speter	return (R_SKIP_ALL);
176417721Speter
176517721Speter    if (!quiet)
176617721Speter	error (0, 0, "Logging %s", update_dir);
176717721Speter    return (R_PROCESS);
176817721Speter}
176925839Speter
177025839Speter/*
177125839Speter * Compare versions.  This is taken from RCS compartial.
177225839Speter */
177325839Speterstatic int
177425839Speterversion_compare (v1, v2, len)
177525839Speter    const char *v1;
177625839Speter    const char *v2;
177725839Speter    int len;
177825839Speter{
177925839Speter    while (1)
178025839Speter    {
178125839Speter	int d1, d2, r;
178225839Speter
178325839Speter	if (*v1 == '\0')
178425839Speter	    return 1;
178525839Speter	if (*v2 == '\0')
178625839Speter	    return -1;
178725839Speter
178825839Speter	while (*v1 == '0')
178925839Speter	    ++v1;
179054427Speter	for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
179125839Speter	    ;
179225839Speter
179325839Speter	while (*v2 == '0')
179425839Speter	    ++v2;
179554427Speter	for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
179625839Speter	    ;
179725839Speter
179825839Speter	if (d1 != d2)
179925839Speter	    return d1 < d2 ? -1 : 1;
180025839Speter
180125839Speter	r = memcmp (v1, v2, d1);
180225839Speter	if (r != 0)
180325839Speter	    return r;
180425839Speter
180525839Speter	--len;
180625839Speter	if (len == 0)
180725839Speter	    return 0;
180825839Speter
180925839Speter	v1 += d1;
181025839Speter	v2 += d1;
181125839Speter
181225839Speter	if (*v1 == '.')
181325839Speter	    ++v1;
181425839Speter	if (*v2 == '.')
181525839Speter	    ++v2;
181625839Speter    }
181725839Speter}
1818