117721Speter/*
2177397Sobrien * Copyright (C) 1986-2008 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$
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;
92177397Sobrien    /* Nonzero if the -b option was seen, meaning that 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",
161177397Sobrien    "\t-b\tList 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",
166177397Sobrien    "\t-n\tList tags (default).\n",
167175273Sobrien    "\t-S\tDo not print name/header if no revisions selected.  -d, -r,\n",
168175273Sobrien    "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
169175273Sobrien    "\t\t-t without this option.\n",
170102840Speter    "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
17181404Speter    "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
172102840Speter    "\t   rev1::rev2  Between rev1 and rev2, excluding rev1.\n",
17381404Speter    "\t   rev:        rev and following revisions on the same branch.\n",
17481404Speter    "\t   rev::       After rev on the same branch.\n",
17581404Speter    "\t   :rev        rev and previous revisions on the same branch.\n",
176102840Speter    "\t   ::rev       rev and previous revisions on the same branch.\n",
17781404Speter    "\t   rev         Just rev.\n",
17881404Speter    "\t   branch      All revisions on the branch.\n",
17981404Speter    "\t   branch.     The last revision on the branch.\n",
180102840Speter    "\t-d dates\tA semicolon-separated list of dates\n",
181102840Speter    "\t        \t(D1<D2 for range, D for latest before).\n",
18225839Speter    "\t-s states\tOnly list revisions with specified states.\n",
18325839Speter    "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
18432785Speter    "(Specify the --help global option for a list of other help options)\n",
18517721Speter    NULL
18617721Speter};
18717721Speter
18866525Speter#ifdef CLIENT_SUPPORT
18966525Speter
19066525Speter/* Helper function for send_arg_list.  */
19166525Speterstatic int send_one PROTO ((Node *, void *));
19266525Speter
19366525Speterstatic int
19466525Spetersend_one (node, closure)
19566525Speter    Node *node;
19666525Speter    void *closure;
19766525Speter{
19866525Speter    char *option = (char *) closure;
19966525Speter
20066525Speter    send_to_server ("Argument ", 0);
20166525Speter    send_to_server (option, 0);
20266525Speter    if (strcmp (node->key, "@@MYSELF") == 0)
20366525Speter	/* It is a bare -w option.  Note that we must send it as
20466525Speter	   -w rather than messing with getcaller() or something (which on
20566525Speter	   the client will return garbage).  */
20666525Speter	;
20766525Speter    else
20866525Speter	send_to_server (node->key, 0);
20966525Speter    send_to_server ("\012", 0);
21066525Speter    return 0;
21166525Speter}
21266525Speter
21366525Speter/* For each element in ARG, send an argument consisting of OPTION
21466525Speter   concatenated with that element.  */
21566525Speterstatic void send_arg_list PROTO ((char *, List *));
21666525Speter
21766525Speterstatic void
21866525Spetersend_arg_list (option, arg)
21966525Speter    char *option;
22066525Speter    List *arg;
22166525Speter{
22266525Speter    if (arg == NULL)
22366525Speter	return;
22466525Speter    walklist (arg, send_one, (void *)option);
22566525Speter}
22666525Speter
22766525Speter#endif
22866525Speter
22917721Speterint
23017721Spetercvslog (argc, argv)
23117721Speter    int argc;
23217721Speter    char **argv;
23317721Speter{
23425839Speter    int c;
23517721Speter    int err = 0;
23617721Speter    int local = 0;
23766525Speter    struct option_revlist **prl;
23817721Speter
239128266Speter    is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
24081404Speter
24117721Speter    if (argc == -1)
24217721Speter	usage (log_usage);
24317721Speter
24425839Speter    memset (&log_data, 0, sizeof log_data);
24566525Speter    prl = &log_data.revlist;
24617721Speter
24726065Speter    optind = 0;
248165111Sobrien    while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1)
24925839Speter    {
25025839Speter	switch (c)
25125839Speter	{
25225839Speter	    case 'b':
25325839Speter		log_data.default_branch = 1;
25425839Speter		break;
25525839Speter	    case 'd':
25625839Speter		log_parse_date (&log_data, optarg);
25725839Speter		break;
25825839Speter	    case 'h':
25925839Speter		log_data.header = 1;
26025839Speter		break;
26125839Speter	    case 'l':
26225839Speter		local = 1;
26325839Speter		break;
26425839Speter	    case 'N':
26525839Speter		log_data.notags = 1;
26625839Speter		break;
267165111Sobrien	    case 'n':
268165111Sobrien		log_data.notags = 0;
269165111Sobrien		break;
270102840Speter	    case 'S':
271102840Speter		log_data.sup_header = 1;
272102840Speter		break;
27325839Speter	    case 'R':
27425839Speter		log_data.nameonly = 1;
27525839Speter		break;
27625839Speter	    case 'r':
27766525Speter		*prl = log_parse_revlist (optarg);
27866525Speter		prl = &(*prl)->next;
27925839Speter		break;
28025839Speter	    case 's':
28125839Speter		log_parse_list (&log_data.statelist, optarg);
28225839Speter		break;
28325839Speter	    case 't':
28425839Speter		log_data.long_header = 1;
28525839Speter		break;
28625839Speter	    case 'w':
28725839Speter		if (optarg != NULL)
28825839Speter		    log_parse_list (&log_data.authorlist, optarg);
28925839Speter		else
29066525Speter		    log_parse_list (&log_data.authorlist, "@@MYSELF");
29125839Speter		break;
29225839Speter	    case '?':
29325839Speter	    default:
29425839Speter		usage (log_usage);
29525839Speter		break;
29625839Speter	}
29725839Speter    }
29881404Speter    argc -= optind;
29981404Speter    argv += optind;
30025839Speter
30117721Speter    wrap_setup ();
30217721Speter
30317721Speter#ifdef CLIENT_SUPPORT
30481404Speter    if (current_parsed_root->isremote)
30525839Speter    {
30666525Speter	struct datelist *p;
30766525Speter	struct option_revlist *rp;
30866525Speter	char datetmp[MAXDATELEN];
30925839Speter
31017721Speter	/* We're the local client.  Fire up the remote server.  */
31117721Speter	start_server ();
31281404Speter
31381404Speter	if (is_rlog && !supported_request ("rlog"))
31481404Speter	    error (1, 0, "server does not support rlog");
31581404Speter
31617721Speter	ign_setup ();
31717721Speter
31866525Speter	if (log_data.default_branch)
31966525Speter	    send_arg ("-b");
32017721Speter
32166525Speter	while (log_data.datelist != NULL)
32266525Speter	{
32366525Speter	    p = log_data.datelist;
32466525Speter	    log_data.datelist = p->next;
325175273Sobrien	    assert (p->start != NULL && p->end != NULL);
32666525Speter	    send_to_server ("Argument -d\012", 0);
32766525Speter	    send_to_server ("Argument ", 0);
32866525Speter	    date_to_internet (datetmp, p->start);
32966525Speter	    send_to_server (datetmp, 0);
33066525Speter	    if (p->inclusive)
33166525Speter		send_to_server ("<=", 0);
33266525Speter	    else
33366525Speter		send_to_server ("<", 0);
33466525Speter	    date_to_internet (datetmp, p->end);
33566525Speter	    send_to_server (datetmp, 0);
33666525Speter	    send_to_server ("\012", 0);
337175273Sobrien	    free (p->start);
338175273Sobrien	    free (p->end);
33966525Speter	    free (p);
34066525Speter	}
34166525Speter	while (log_data.singledatelist != NULL)
34266525Speter	{
34366525Speter	    p = log_data.singledatelist;
34466525Speter	    log_data.singledatelist = p->next;
345175273Sobrien	    assert (p->end != NULL);
34666525Speter	    send_to_server ("Argument -d\012", 0);
34766525Speter	    send_to_server ("Argument ", 0);
34866525Speter	    date_to_internet (datetmp, p->end);
34966525Speter	    send_to_server (datetmp, 0);
35066525Speter	    send_to_server ("\012", 0);
351175273Sobrien	    free (p->end);
35266525Speter	    free (p);
35366525Speter	}
35466525Speter
35566525Speter	if (log_data.header)
35666525Speter	    send_arg ("-h");
35766525Speter	if (local)
35866525Speter	    send_arg("-l");
35966525Speter	if (log_data.notags)
36066525Speter	    send_arg("-N");
361102840Speter	if (log_data.sup_header)
362102840Speter	    send_arg("-S");
36366525Speter	if (log_data.nameonly)
36466525Speter	    send_arg("-R");
36566525Speter	if (log_data.long_header)
36666525Speter	    send_arg("-t");
36717721Speter
36866525Speter	while (log_data.revlist != NULL)
36966525Speter	{
37066525Speter	    rp = log_data.revlist;
37166525Speter	    log_data.revlist = rp->next;
37266525Speter	    send_to_server ("Argument -r", 0);
37366525Speter	    if (rp->branchhead)
37466525Speter	    {
37566525Speter		if (rp->first != NULL)
37666525Speter		    send_to_server (rp->first, 0);
37766525Speter		send_to_server (".", 1);
37866525Speter	    }
37966525Speter	    else
38066525Speter	    {
38166525Speter		if (rp->first != NULL)
38266525Speter		    send_to_server (rp->first, 0);
38366525Speter		send_to_server (":", 1);
38481404Speter		if (!rp->inclusive)
38581404Speter		    send_to_server (":", 1);
38666525Speter		if (rp->last != NULL)
38766525Speter		    send_to_server (rp->last, 0);
38866525Speter	    }
38966525Speter	    send_to_server ("\012", 0);
39066525Speter	    if (rp->first)
39166525Speter		free (rp->first);
39266525Speter	    if (rp->last)
39366525Speter		free (rp->last);
39466525Speter	    free (rp);
39566525Speter	}
39666525Speter	send_arg_list ("-s", log_data.statelist);
39766525Speter	dellist (&log_data.statelist);
39866525Speter	send_arg_list ("-w", log_data.authorlist);
39966525Speter	dellist (&log_data.authorlist);
400107484Speter	send_arg ("--");
40166525Speter
40281404Speter	if (is_rlog)
40381404Speter	{
40481404Speter	    int i;
40581404Speter	    for (i = 0; i < argc; i++)
40681404Speter		send_arg (argv[i]);
40781404Speter	    send_to_server ("rlog\012", 0);
40881404Speter	}
40981404Speter	else
41081404Speter	{
41181404Speter	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
41281404Speter	    send_file_names (argc, argv, SEND_EXPAND_WILD);
41381404Speter	    send_to_server ("log\012", 0);
41481404Speter	}
41517721Speter        err = get_responses_and_close ();
41617721Speter	return err;
41717721Speter    }
41817721Speter#endif
41917721Speter
42066525Speter    /* OK, now that we know we are local/server, we can resolve @@MYSELF
42166525Speter       into our user name.  */
42266525Speter    if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
42366525Speter	log_parse_list (&log_data.authorlist, getcaller ());
42466525Speter
42581404Speter    if (is_rlog)
42681404Speter    {
42781404Speter	DBM *db;
42881404Speter	int i;
42981404Speter	db = open_module ();
43081404Speter	for (i = 0; i < argc; i++)
43181404Speter	{
43281404Speter	    err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
433102840Speter			     (char *) NULL, 0, local, 0, 0, (char *) NULL);
43481404Speter	}
43581404Speter	close_module (db);
43681404Speter    }
43781404Speter    else
43881404Speter    {
43981404Speter	err = rlog_proc (argc + 1, argv - 1, (char *) NULL,
440102840Speter			 (char *) NULL, (char *) NULL, 0, local, (char *) NULL,
44181404Speter			 (char *) NULL);
44281404Speter    }
44366525Speter
44466525Speter    while (log_data.revlist)
44566525Speter    {
44666525Speter	struct option_revlist *rl = log_data.revlist->next;
44766525Speter	if (log_data.revlist->first)
44866525Speter	    free (log_data.revlist->first);
44966525Speter	if (log_data.revlist->last)
45066525Speter	    free (log_data.revlist->last);
45166525Speter	free (log_data.revlist);
45266525Speter	log_data.revlist = rl;
45366525Speter    }
45466525Speter    while (log_data.datelist)
45566525Speter    {
45666525Speter	struct datelist *nd = log_data.datelist->next;
45766525Speter	if (log_data.datelist->start)
45866525Speter	    free (log_data.datelist->start);
45966525Speter	if (log_data.datelist->end)
46066525Speter	    free (log_data.datelist->end);
46166525Speter	free (log_data.datelist);
46266525Speter	log_data.datelist = nd;
46366525Speter    }
46466525Speter    while (log_data.singledatelist)
46566525Speter    {
46666525Speter	struct datelist *nd = log_data.singledatelist->next;
46766525Speter	if (log_data.singledatelist->start)
46866525Speter	    free (log_data.singledatelist->start);
46966525Speter	if (log_data.singledatelist->end)
47066525Speter	    free (log_data.singledatelist->end);
47166525Speter	free (log_data.singledatelist);
47266525Speter	log_data.singledatelist = nd;
47366525Speter    }
47466525Speter    dellist (&log_data.statelist);
47566525Speter    dellist (&log_data.authorlist);
47666525Speter
47717721Speter    return (err);
47817721Speter}
47917721Speter
48081404Speter
48181404Speterstatic int
48281404Speterrlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
48381404Speter    int argc;
48481404Speter    char **argv;
48581404Speter    char *xwhere;
48681404Speter    char *mwhere;
48781404Speter    char *mfile;
48881404Speter    int shorten;
48981404Speter    int local;
49081404Speter    char *mname;
49181404Speter    char *msg;
49281404Speter{
49381404Speter    /* Begin section which is identical to patch_proc--should this
49481404Speter       be abstracted out somehow?  */
49581404Speter    char *myargv[2];
49681404Speter    int err = 0;
49781404Speter    int which;
49881404Speter    char *repository;
49981404Speter    char *where;
50081404Speter
50181404Speter    if (is_rlog)
50281404Speter    {
503128266Speter	repository = xmalloc (strlen (current_parsed_root->directory)
504128266Speter                              + strlen (argv[0])
50581404Speter			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
506128266Speter	(void)sprintf (repository, "%s/%s",
507128266Speter                       current_parsed_root->directory, argv[0]);
508128266Speter	where = xmalloc (strlen (argv[0])
509128266Speter                         + (mfile == NULL ? 0 : strlen (mfile) + 1)
51081404Speter			 + 1);
51181404Speter	(void) strcpy (where, argv[0]);
51281404Speter
513128266Speter	/* If mfile isn't null, we need to set up to do only part of theu
514128266Speter         * module.
515128266Speter         */
51681404Speter	if (mfile != NULL)
51781404Speter	{
51881404Speter	    char *cp;
51981404Speter	    char *path;
52081404Speter
521128266Speter	    /* If the portion of the module is a path, put the dir part on
522128266Speter             * repos.
523128266Speter             */
52481404Speter	    if ((cp = strrchr (mfile, '/')) != NULL)
52581404Speter	    {
52681404Speter		*cp = '\0';
527128266Speter		(void)strcat (repository, "/");
528128266Speter		(void)strcat (repository, mfile);
529128266Speter		(void)strcat (where, "/");
530128266Speter		(void)strcat (where, mfile);
53181404Speter		mfile = cp + 1;
53281404Speter	    }
53381404Speter
53481404Speter	    /* take care of the rest */
53581404Speter	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
536128266Speter	    (void)sprintf (path, "%s/%s", repository, mfile);
53781404Speter	    if (isdir (path))
53881404Speter	    {
53981404Speter		/* directory means repository gets the dir tacked on */
540128266Speter		(void)strcpy (repository, path);
541128266Speter		(void)strcat (where, "/");
542128266Speter		(void)strcat (where, mfile);
54381404Speter	    }
54481404Speter	    else
54581404Speter	    {
54681404Speter		myargv[0] = argv[0];
54781404Speter		myargv[1] = mfile;
54881404Speter		argc = 2;
54981404Speter		argv = myargv;
55081404Speter	    }
55181404Speter	    free (path);
55281404Speter	}
55381404Speter
55481404Speter	/* cd to the starting repository */
555128266Speter	if (CVS_CHDIR (repository) < 0)
55681404Speter	{
55781404Speter	    error (0, errno, "cannot chdir to %s", repository);
55881404Speter	    free (repository);
559128266Speter	    free (where);
560128266Speter	    return 1;
56181404Speter	}
56281404Speter	/* End section which is identical to patch_proc.  */
56381404Speter
56481404Speter	which = W_REPOS | W_ATTIC;
56581404Speter    }
56681404Speter    else
56781404Speter    {
568128266Speter        repository = NULL;
56981404Speter        where = NULL;
57081404Speter        which = W_LOCAL | W_REPOS | W_ATTIC;
57181404Speter    }
57281404Speter
57381404Speter    err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
57481404Speter			   (DIRLEAVEPROC) NULL, (void *) &log_data,
575109655Speter			   argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
576128266Speter			   where, 1, repository);
577128266Speter
578128266Speter    if (!(which & W_LOCAL)) free (repository);
579128266Speter    if (where) free (where);
580128266Speter
58181404Speter    return err;
58281404Speter}
58381404Speter
58481404Speter
585128266Speter
58625839Speter/*
58725839Speter * Parse a revision list specification.
58825839Speter */
58925839Speterstatic struct option_revlist *
59025839Speterlog_parse_revlist (argstring)
59125839Speter    const char *argstring;
59225839Speter{
59366525Speter    char *orig_copy, *copy;
59425839Speter    struct option_revlist *ret, **pr;
59525839Speter
59625839Speter    /* Unfortunately, rlog accepts -r without an argument to mean that
59725839Speter       latest revision on the default branch, so we must support that
59825839Speter       for compatibility.  */
59925839Speter    if (argstring == NULL)
60066525Speter	argstring = "";
60125839Speter
60225839Speter    ret = NULL;
60325839Speter    pr = &ret;
60425839Speter
60525839Speter    /* Copy the argument into memory so that we can change it.  We
60625839Speter       don't want to change the argument because, at least as of this
60766525Speter       writing, we will use it if we send the arguments to the server.  */
60866525Speter    orig_copy = copy = xstrdup (argstring);
60925839Speter    while (copy != NULL)
61025839Speter    {
61125839Speter	char *comma;
61225839Speter	struct option_revlist *r;
61325839Speter
61425839Speter	comma = strchr (copy, ',');
61525839Speter	if (comma != NULL)
61625839Speter	    *comma++ = '\0';
61725839Speter
61825839Speter	r = (struct option_revlist *) xmalloc (sizeof *r);
61925839Speter	r->next = NULL;
62066525Speter	r->first = copy;
62166525Speter	r->branchhead = 0;
62266525Speter	r->last = strchr (copy, ':');
62366525Speter	if (r->last != NULL)
62481404Speter	{
62566525Speter	    *r->last++ = '\0';
62681404Speter	    r->inclusive = (*r->last != ':');
62781404Speter	    if (!r->inclusive)
62881404Speter		r->last++;
62981404Speter	}
63025839Speter	else
63125839Speter	{
63266525Speter	    r->last = r->first;
63381404Speter	    r->inclusive = 1;
63466525Speter	    if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
63566525Speter	    {
63666525Speter		r->branchhead = 1;
63766525Speter		r->first[strlen (r->first) - 1] = '\0';
63866525Speter	    }
63925839Speter	}
64025839Speter
64166525Speter	if (*r->first == '\0')
64266525Speter	    r->first = NULL;
64366525Speter	if (*r->last == '\0')
64466525Speter	    r->last = NULL;
64566525Speter
64666525Speter	if (r->first != NULL)
64766525Speter	    r->first = xstrdup (r->first);
64866525Speter	if (r->last != NULL)
64966525Speter	    r->last = xstrdup (r->last);
65066525Speter
65125839Speter	*pr = r;
65225839Speter	pr = &r->next;
65325839Speter
65425839Speter	copy = comma;
65525839Speter    }
65625839Speter
65766525Speter    free (orig_copy);
65825839Speter    return ret;
65925839Speter}
66025839Speter
66117721Speter/*
66225839Speter * Parse a date specification.
66325839Speter */
66425839Speterstatic void
66525839Speterlog_parse_date (log_data, argstring)
66625839Speter    struct log_data *log_data;
66725839Speter    const char *argstring;
66825839Speter{
66925839Speter    char *orig_copy, *copy;
67025839Speter
67125839Speter    /* Copy the argument into memory so that we can change it.  We
67225839Speter       don't want to change the argument because, at least as of this
67325839Speter       writing, we will use it if we send the arguments to the server.  */
67466525Speter    orig_copy = copy = xstrdup (argstring);
67525839Speter    while (copy != NULL)
67625839Speter    {
67725839Speter	struct datelist *nd, **pd;
67825839Speter	char *cpend, *cp, *ds, *de;
67925839Speter
68025839Speter	nd = (struct datelist *) xmalloc (sizeof *nd);
68125839Speter
68225839Speter	cpend = strchr (copy, ';');
68325839Speter	if (cpend != NULL)
68425839Speter	    *cpend++ = '\0';
68525839Speter
68625839Speter	pd = &log_data->datelist;
68725839Speter	nd->inclusive = 0;
68825839Speter
68925839Speter	if ((cp = strchr (copy, '>')) != NULL)
69025839Speter	{
69125839Speter	    *cp++ = '\0';
69225839Speter	    if (*cp == '=')
69325839Speter	    {
69425839Speter		++cp;
69525839Speter		nd->inclusive = 1;
69625839Speter	    }
69725839Speter	    ds = cp;
69825839Speter	    de = copy;
69925839Speter	}
70025839Speter	else if ((cp = strchr (copy, '<')) != NULL)
70125839Speter	{
70225839Speter	    *cp++ = '\0';
70325839Speter	    if (*cp == '=')
70425839Speter	    {
70525839Speter		++cp;
70625839Speter		nd->inclusive = 1;
70725839Speter	    }
70825839Speter	    ds = copy;
70925839Speter	    de = cp;
71025839Speter	}
71125839Speter	else
71225839Speter	{
71325839Speter	    ds = NULL;
71425839Speter	    de = copy;
71525839Speter	    pd = &log_data->singledatelist;
71625839Speter	}
71725839Speter
71825839Speter	if (ds == NULL)
71925839Speter	    nd->start = NULL;
72025839Speter	else if (*ds != '\0')
72125839Speter	    nd->start = Make_Date (ds);
72225839Speter	else
72325839Speter	{
72425839Speter	  /* 1970 was the beginning of time, as far as get_date and
72525839Speter	     Make_Date are concerned.  FIXME: That is true only if time_t
72625839Speter	     is a POSIX-style time and there is nothing in ANSI that
72725839Speter	     mandates that.  It would be cleaner to set a flag saying
72825839Speter	     whether or not there is a start date.  */
72925839Speter	    nd->start = Make_Date ("1/1/1970 UTC");
73025839Speter	}
73125839Speter
73225839Speter	if (*de != '\0')
73325839Speter	    nd->end = Make_Date (de);
73425839Speter	else
73525839Speter	{
73625839Speter	    /* We want to set the end date to some time sufficiently far
73725839Speter	       in the future to pick up all revisions that have been
73825839Speter	       created since the specified date and the time `cvs log'
73925839Speter	       completes.  FIXME: The date in question only makes sense
74025839Speter	       if time_t is a POSIX-style time and it is 32 bits
74125839Speter	       and signed.  We should instead be setting a flag saying
74225839Speter	       whether or not there is an end date.  Note that using
74325839Speter	       something like "next week" would break the testsuite (and,
74425839Speter	       perhaps less importantly, loses if the clock is set grossly
74525839Speter	       wrong).  */
74625839Speter	    nd->end = Make_Date ("2038-01-01");
74725839Speter	}
74825839Speter
74925839Speter	nd->next = *pd;
75025839Speter	*pd = nd;
75125839Speter
75225839Speter	copy = cpend;
75325839Speter    }
75425839Speter
75525839Speter    free (orig_copy);
75625839Speter}
75725839Speter
75825839Speter/*
75925839Speter * Parse a comma separated list of items, and add each one to *PLIST.
76025839Speter */
76125839Speterstatic void
76225839Speterlog_parse_list (plist, argstring)
76325839Speter    List **plist;
76425839Speter    const char *argstring;
76525839Speter{
76625839Speter    while (1)
76725839Speter    {
76825839Speter	Node *p;
76925839Speter	char *cp;
77025839Speter
77125839Speter	p = getnode ();
77225839Speter
77325839Speter	cp = strchr (argstring, ',');
77425839Speter	if (cp == NULL)
77525839Speter	    p->key = xstrdup (argstring);
77625839Speter	else
77725839Speter	{
77825839Speter	    size_t len;
77925839Speter
78025839Speter	    len = cp - argstring;
78125839Speter	    p->key = xmalloc (len + 1);
78225839Speter	    strncpy (p->key, argstring, len);
783107484Speter	    p->key[len] = '\0';
78425839Speter	}
78525839Speter
78625839Speter	if (*plist == NULL)
78725839Speter	    *plist = getlist ();
78825839Speter	if (addnode (*plist, p) != 0)
78925839Speter	    freenode (p);
79025839Speter
79125839Speter	if (cp == NULL)
79225839Speter	    break;
79325839Speter
79425839Speter	argstring = cp + 1;
79525839Speter    }
79625839Speter}
79725839Speter
79832785Speterstatic int printlock_proc PROTO ((Node *, void *));
79932785Speter
80032785Speterstatic int
80132785Speterprintlock_proc (lock, foo)
80232785Speter    Node *lock;
80332785Speter    void *foo;
80432785Speter{
80532785Speter    cvs_output ("\n\t", 2);
80632785Speter    cvs_output (lock->data, 0);
80732785Speter    cvs_output (": ", 2);
80832785Speter    cvs_output (lock->key, 0);
80932785Speter    return 0;
81032785Speter}
81132785Speter
812128266Speter
813128266Speter
81425839Speter/*
81517721Speter * Do an rlog on a file
81617721Speter */
81717721Speterstatic int
81825839Speterlog_fileproc (callerdat, finfo)
81925839Speter    void *callerdat;
82017721Speter    struct file_info *finfo;
82117721Speter{
82225839Speter    struct log_data *log_data = (struct log_data *) callerdat;
82317721Speter    Node *p;
824175273Sobrien    char *baserev;
825102840Speter    int selrev = -1;
82617721Speter    RCSNode *rcsfile;
82725839Speter    char buf[50];
828128266Speter    struct revlist *revlist = NULL;
82925839Speter    struct log_data_and_rcs log_data_and_rcs;
83017721Speter
831175273Sobrien    rcsfile = finfo->rcs;
832175273Sobrien    p = findnode (finfo->entries, finfo->file);
833175273Sobrien    if (p != NULL)
83417721Speter    {
835175273Sobrien	Entnode *e = p->data;
836175273Sobrien	baserev = e->version;
837175273Sobrien	if (baserev[0] == '-') ++baserev;
838175273Sobrien    }
839175273Sobrien    else
840175273Sobrien	baserev = NULL;
841175273Sobrien
842175273Sobrien    if (rcsfile == NULL)
843175273Sobrien    {
84417721Speter	/* no rcs file.  What *do* we know about this file? */
845175273Sobrien	if (baserev != NULL)
84617721Speter	{
847175273Sobrien	    if (baserev[0] == '0' && baserev[1] == '\0')
84817721Speter	    {
84917721Speter		if (!really_quiet)
85017721Speter		    error (0, 0, "%s has been added, but not committed",
85117721Speter			   finfo->file);
852128266Speter		return 0;
85317721Speter	    }
85417721Speter	}
85517721Speter
85617721Speter	if (!really_quiet)
85717721Speter	    error (0, 0, "nothing known about %s", finfo->file);
85817721Speter
859128266Speter	return 1;
86017721Speter    }
86117721Speter
862102840Speter    if (log_data->sup_header || !log_data->nameonly)
863102840Speter    {
864102840Speter
865102840Speter	/* We will need all the information in the RCS file.  */
866102840Speter	RCS_fully_parse (rcsfile);
867102840Speter
868102840Speter	/* Turn any symbolic revisions in the revision list into numeric
869102840Speter	   revisions.  */
870175273Sobrien	revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist,
871102840Speter				      log_data->default_branch);
872128266Speter	if (log_data->sup_header
873128266Speter            || (!log_data->header && !log_data->long_header))
874102840Speter	{
875102840Speter	    log_data_and_rcs.log_data = log_data;
876102840Speter	    log_data_and_rcs.revlist = revlist;
877102840Speter	    log_data_and_rcs.rcs = rcsfile;
878102840Speter
879102840Speter	    /* If any single dates were specified, we need to identify the
880102840Speter	       revisions they select.  Each one selects the single
881102840Speter	       revision, which is otherwise selected, of that date or
882102840Speter	       earlier.  The log_fix_singledate routine will fill in the
883102840Speter	       start date for each specific revision.  */
884102840Speter	    if (log_data->singledatelist != NULL)
885102840Speter		walklist (rcsfile->versions, log_fix_singledate,
886128266Speter			  (void *)&log_data_and_rcs);
887102840Speter
888102840Speter	    selrev = walklist (rcsfile->versions, log_count_print,
889128266Speter			       (void *)&log_data_and_rcs);
890128266Speter	    if (log_data->sup_header && selrev == 0)
891128266Speter	    {
892128266Speter		log_free_revlist (revlist);
893128266Speter		return 0;
894128266Speter	    }
895102840Speter	}
896102840Speter
897102840Speter    }
898102840Speter
89925839Speter    if (log_data->nameonly)
90017721Speter    {
90125839Speter	cvs_output (rcsfile->path, 0);
90225839Speter	cvs_output ("\n", 1);
903128266Speter	log_free_revlist (revlist);
90425839Speter	return 0;
90517721Speter    }
90617721Speter
90725839Speter    /* The output here is intended to be exactly compatible with the
90825839Speter       output of rlog.  I'm not sure whether this code should be here
90925839Speter       or in rcs.c; I put it here because it is specific to the log
91025839Speter       function, even though it uses information gathered by the
91125839Speter       functions in rcs.c.  */
91225839Speter
91325839Speter    cvs_output ("\n", 1);
91425839Speter
91525839Speter    cvs_output ("RCS file: ", 0);
91625839Speter    cvs_output (rcsfile->path, 0);
91725839Speter
91881404Speter    if (!is_rlog)
91917721Speter    {
92081404Speter	cvs_output ("\nWorking file: ", 0);
92181404Speter	if (finfo->update_dir[0] != '\0')
92281404Speter	{
92381404Speter	    cvs_output (finfo->update_dir, 0);
92481404Speter	    cvs_output ("/", 0);
92581404Speter	}
92625839Speter	cvs_output (finfo->file, 0);
92717721Speter    }
92817721Speter
92925839Speter    cvs_output ("\nhead:", 0);
93025839Speter    if (rcsfile->head != NULL)
93117721Speter    {
93225839Speter	cvs_output (" ", 1);
93325839Speter	cvs_output (rcsfile->head, 0);
93417721Speter    }
93525839Speter
93625839Speter    cvs_output ("\nbranch:", 0);
93725839Speter    if (rcsfile->branch != NULL)
93825839Speter    {
93925839Speter	cvs_output (" ", 1);
94025839Speter	cvs_output (rcsfile->branch, 0);
94125839Speter    }
94225839Speter
94325839Speter    cvs_output ("\nlocks:", 0);
94432785Speter    if (rcsfile->strict_locks)
94532785Speter	cvs_output (" strict", 0);
94632785Speter    walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
94725839Speter
94825839Speter    cvs_output ("\naccess list:", 0);
94932785Speter    if (rcsfile->access != NULL)
95025839Speter    {
95132785Speter	const char *cp;
95232785Speter
95332785Speter	cp = rcsfile->access;
95432785Speter	while (*cp != '\0')
95525839Speter	{
95625839Speter		const char *cp2;
95725839Speter
95825839Speter		cvs_output ("\n\t", 2);
95925839Speter		cp2 = cp;
960128266Speter		while (!isspace ((unsigned char) *cp2) && *cp2 != '\0')
96125839Speter		    ++cp2;
96225839Speter		cvs_output (cp, cp2 - cp);
96325839Speter		cp = cp2;
96454427Speter		while (isspace ((unsigned char) *cp) && *cp != '\0')
96525839Speter		    ++cp;
96625839Speter	}
96725839Speter    }
96825839Speter
969128266Speter    if (!log_data->notags)
97025839Speter    {
97125839Speter	List *syms;
97225839Speter
97325839Speter	cvs_output ("\nsymbolic names:", 0);
97425839Speter	syms = RCS_symbols (rcsfile);
97525839Speter	walklist (syms, log_symbol, NULL);
97625839Speter    }
97725839Speter
97825839Speter    cvs_output ("\nkeyword substitution: ", 0);
97925839Speter    if (rcsfile->expand == NULL)
98025839Speter	cvs_output ("kv", 2);
98125839Speter    else
98225839Speter	cvs_output (rcsfile->expand, 0);
98325839Speter
98425839Speter    cvs_output ("\ntotal revisions: ", 0);
98525839Speter    sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
98625839Speter    cvs_output (buf, 0);
98725839Speter
988102840Speter    if (selrev >= 0)
98925839Speter    {
99025839Speter	cvs_output (";\tselected revisions: ", 0);
991102840Speter	sprintf (buf, "%d", selrev);
99225839Speter	cvs_output (buf, 0);
99325839Speter    }
99425839Speter
99525839Speter    cvs_output ("\n", 1);
99625839Speter
997128266Speter    if (!log_data->header || log_data->long_header)
99825839Speter    {
99925839Speter	cvs_output ("description:\n", 0);
100032785Speter	if (rcsfile->desc != NULL)
100132785Speter	    cvs_output (rcsfile->desc, 0);
100225839Speter    }
100325839Speter
1004128266Speter    if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
100525839Speter    {
100625839Speter	p = findnode (rcsfile->versions, rcsfile->head);
100725839Speter	if (p == NULL)
100825839Speter	    error (1, 0, "can not find head revision in `%s'",
100925839Speter		   finfo->fullname);
101025839Speter	while (p != NULL)
101125839Speter	{
1012128266Speter	    RCSVers *vers = p->data;
101325839Speter
101425839Speter	    log_version (log_data, revlist, rcsfile, vers, 1);
101525839Speter	    if (vers->next == NULL)
101625839Speter		p = NULL;
101725839Speter	    else
101825839Speter	    {
101925839Speter		p = findnode (rcsfile->versions, vers->next);
102025839Speter		if (p == NULL)
102125839Speter		    error (1, 0, "can not find next revision `%s' in `%s'",
102225839Speter			   vers->next, finfo->fullname);
102325839Speter	    }
102425839Speter	}
102525839Speter
102625839Speter	log_tree (log_data, revlist, rcsfile, rcsfile->head);
102725839Speter    }
102825839Speter
102925839Speter    cvs_output("\
103025839Speter=============================================================================\n",
103125839Speter	       0);
103225839Speter
103325839Speter    /* Free up the new revlist and restore the old one.  */
103425839Speter    log_free_revlist (revlist);
103525839Speter
103625839Speter    /* If singledatelist is not NULL, free up the start dates we added
103725839Speter       to it.  */
103825839Speter    if (log_data->singledatelist != NULL)
103925839Speter    {
104025839Speter	struct datelist *d;
104125839Speter
104225839Speter	for (d = log_data->singledatelist; d != NULL; d = d->next)
104325839Speter	{
104425839Speter	    if (d->start != NULL)
104525839Speter		free (d->start);
104625839Speter	    d->start = NULL;
104725839Speter	}
104825839Speter    }
104925839Speter
105025839Speter    return 0;
105117721Speter}
105217721Speter
1053128266Speter
1054128266Speter
105517721Speter/*
105625839Speter * Fix up a revision list in order to compare it against versions.
105725839Speter * Expand any symbolic revisions.
105825839Speter */
105925839Speterstatic struct revlist *
1060175273Sobrienlog_expand_revlist (rcs, baserev, revlist, default_branch)
106125839Speter    RCSNode *rcs;
1062175273Sobrien    char *baserev;
106325839Speter    struct option_revlist *revlist;
106425839Speter    int default_branch;
106525839Speter{
106625839Speter    struct option_revlist *r;
106725839Speter    struct revlist *ret, **pr;
106825839Speter
106925839Speter    ret = NULL;
107025839Speter    pr = &ret;
107125839Speter    for (r = revlist; r != NULL; r = r->next)
107225839Speter    {
107325839Speter	struct revlist *nr;
107425839Speter
107525839Speter	nr = (struct revlist *) xmalloc (sizeof *nr);
107681404Speter	nr->inclusive = r->inclusive;
107725839Speter
107825839Speter	if (r->first == NULL && r->last == NULL)
107925839Speter	{
108025839Speter	    /* If both first and last are NULL, it means that we want
108125839Speter	       just the head of the default branch, which is RCS_head.  */
108225839Speter	    nr->first = RCS_head (rcs);
1083175273Sobrien	    if (!nr->first)
1084175273Sobrien	    {
1085175273Sobrien		if (!really_quiet)
1086175273Sobrien		    error (0, 0, "No head revision in archive `%s'.",
1087175273Sobrien		           rcs->path);
1088175273Sobrien		nr->last = NULL;
1089175273Sobrien		nr->fields = 0;
1090175273Sobrien	    }
1091175273Sobrien	    else
1092175273Sobrien	    {
1093175273Sobrien		nr->last = xstrdup (nr->first);
1094175273Sobrien		nr->fields = numdots (nr->first) + 1;
1095175273Sobrien	    }
109625839Speter	}
109725839Speter	else if (r->branchhead)
109825839Speter	{
109925839Speter	    char *branch;
110025839Speter
1101175273Sobrien	    assert (r->first != NULL);
1102175273Sobrien
110325839Speter	    /* Print just the head of the branch.  */
110454427Speter	    if (isdigit ((unsigned char) r->first[0]))
110525839Speter		nr->first = RCS_getbranch (rcs, r->first, 1);
110625839Speter	    else
110725839Speter	    {
110825839Speter		branch = RCS_whatbranch (rcs, r->first);
110925839Speter		if (branch == NULL)
1110102840Speter		    nr->first = NULL;
1111102840Speter		else
111225839Speter		{
1113102840Speter		    nr->first = RCS_getbranch (rcs, branch, 1);
1114102840Speter		    free (branch);
111525839Speter		}
111625839Speter	    }
1117175273Sobrien	    if (!nr->first)
111825839Speter	    {
1119175273Sobrien		if (!really_quiet)
1120175273Sobrien		    error (0, 0, "warning: no branch `%s' in `%s'",
1121175273Sobrien			   r->first, rcs->path);
1122102840Speter		nr->last = NULL;
1123102840Speter		nr->fields = 0;
112425839Speter	    }
1125102840Speter	    else
1126102840Speter	    {
1127102840Speter		nr->last = xstrdup (nr->first);
1128102840Speter		nr->fields = numdots (nr->first) + 1;
1129102840Speter	    }
113025839Speter	}
113125839Speter	else
113225839Speter	{
113354427Speter	    if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
113425839Speter		nr->first = xstrdup (r->first);
113525839Speter	    else
113625839Speter	    {
1137175273Sobrien		if (baserev && strcmp (r->first, TAG_BASE) == 0)
1138175273Sobrien		    nr->first = xstrdup (baserev);
1139175273Sobrien		else if (RCS_nodeisbranch (rcs, r->first))
114025839Speter		    nr->first = RCS_whatbranch (rcs, r->first);
114125839Speter		else
114225839Speter		    nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
1143130303Speter		if (nr->first == NULL && !really_quiet)
114425839Speter		{
114525839Speter		    error (0, 0, "warning: no revision `%s' in `%s'",
114625839Speter			   r->first, rcs->path);
114725839Speter		}
114825839Speter	    }
114925839Speter
1150102840Speter	    if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1151102840Speter					strcmp (r->last, r->first) == 0))
115225839Speter		nr->last = xstrdup (nr->first);
115354427Speter	    else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
115425839Speter		nr->last = xstrdup (r->last);
115525839Speter	    else
115625839Speter	    {
1157175273Sobrien		if (baserev && strcmp (r->last, TAG_BASE) == 0)
1158175273Sobrien		    nr->last = xstrdup (baserev);
1159175273Sobrien		else if (RCS_nodeisbranch (rcs, r->last))
116025839Speter		    nr->last = RCS_whatbranch (rcs, r->last);
116125839Speter		else
116225839Speter		    nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
1163130303Speter		if (nr->last == NULL && !really_quiet)
116425839Speter		{
116525839Speter		    error (0, 0, "warning: no revision `%s' in `%s'",
116625839Speter			   r->last, rcs->path);
116725839Speter		}
116825839Speter	    }
116925839Speter
117025839Speter	    /* Process the revision numbers the same way that rlog
117125839Speter               does.  This code is a bit cryptic for my tastes, but
117225839Speter               keeping the same implementation as rlog ensures a
117325839Speter               certain degree of compatibility.  */
1174128266Speter	    if (r->first == NULL && nr->last != NULL)
117525839Speter	    {
1176128266Speter		nr->fields = numdots (nr->last) + 1;
1177128266Speter		if (nr->fields < 2)
1178128266Speter		    nr->first = xstrdup (".0");
117925839Speter		else
118025839Speter		{
1181128266Speter		    char *cp;
118225839Speter
1183128266Speter		    nr->first = xstrdup (nr->last);
1184128266Speter		    cp = strrchr (nr->first, '.');
1185175273Sobrien		    assert (cp);
1186128266Speter		    strcpy (cp + 1, "0");
118725839Speter		}
118825839Speter	    }
1189128266Speter	    else if (r->last == NULL && nr->first != NULL)
119025839Speter	    {
119125839Speter		nr->fields = numdots (nr->first) + 1;
119225839Speter		nr->last = xstrdup (nr->first);
119325839Speter		if (nr->fields < 2)
119425839Speter		    nr->last[0] = '\0';
119525839Speter		else
119625839Speter		{
119725839Speter		    char *cp;
119825839Speter
119925839Speter		    cp = strrchr (nr->last, '.');
1200175273Sobrien		    assert (cp);
120125839Speter		    *cp = '\0';
120225839Speter		}
120325839Speter	    }
1204107484Speter	    else if (nr->first == NULL || nr->last == NULL)
1205107484Speter		nr->fields = 0;
1206107484Speter	    else if (strcmp (nr->first, nr->last) == 0)
1207107484Speter		nr->fields = numdots (nr->last) + 1;
1208107484Speter	    else
120925839Speter	    {
1210107484Speter		int ord;
1211107484Speter		int dots1 = numdots (nr->first);
1212107484Speter		int dots2 = numdots (nr->last);
1213107484Speter		if (dots1 > dots2 || (dots1 == dots2 &&
1214107484Speter		    version_compare (nr->first, nr->last, dots1 + 1) > 0))
121525839Speter		{
1216107484Speter		    char *tmp = nr->first;
1217107484Speter		    nr->first = nr->last;
1218107484Speter		    nr->last = tmp;
1219107484Speter		    nr->fields = dots2 + 1;
1220107484Speter		    dots2 = dots1;
1221107484Speter		    dots1 = nr->fields - 1;
1222107484Speter		}
1223107484Speter		else
1224107484Speter		    nr->fields = dots1 + 1;
1225107484Speter		dots1 += (nr->fields & 1);
1226107484Speter		ord = version_compare (nr->first, nr->last, dots1);
1227107484Speter		if (ord > 0 || (nr->fields > 2 && ord < 0))
1228107484Speter		{
122925839Speter		    error (0, 0,
123025839Speter			   "invalid branch or revision pair %s:%s in `%s'",
123125839Speter			   r->first, r->last, rcs->path);
123225839Speter		    free (nr->first);
1233102840Speter		    nr->first = NULL;
123425839Speter		    free (nr->last);
1235102840Speter		    nr->last = NULL;
1236102840Speter		    nr->fields = 0;
123725839Speter		}
1238107484Speter		else
123925839Speter		{
1240107484Speter		    if (nr->fields <= dots2 && (nr->fields & 1))
1241107484Speter		    {
1242107484Speter			char *p = xmalloc (strlen (nr->first) + 3);
1243107484Speter			strcpy (p, nr->first);
1244107484Speter			strcat (p, ".0");
1245107484Speter			free (nr->first);
1246107484Speter			nr->first = p;
1247107484Speter			++nr->fields;
1248107484Speter		    }
1249107484Speter		    while (nr->fields <= dots2)
1250107484Speter		    {
1251107484Speter			char *p;
1252107484Speter			int i;
125325839Speter
1254107484Speter			nr->next = NULL;
1255107484Speter			*pr = nr;
1256107484Speter			nr = (struct revlist *) xmalloc (sizeof *nr);
1257107484Speter			nr->inclusive = 1;
1258107484Speter			nr->first = xstrdup ((*pr)->last);
1259107484Speter			nr->last = xstrdup ((*pr)->last);
1260107484Speter			nr->fields = (*pr)->fields;
1261107484Speter			p = (*pr)->last;
1262107484Speter			for (i = 0; i < nr->fields; i++)
1263107484Speter			    p = strchr (p, '.') + 1;
1264107484Speter			p[-1] = '\0';
1265107484Speter			p = strchr (nr->first + (p - (*pr)->last), '.');
1266107484Speter			if (p != NULL)
1267107484Speter			{
1268107484Speter			    *++p = '0';
1269107484Speter			    *++p = '\0';
1270107484Speter			    nr->fields += 2;
1271107484Speter			}
1272107484Speter			else
1273107484Speter			    ++nr->fields;
1274107484Speter			pr = &(*pr)->next;
1275107484Speter		    }
127625839Speter		}
127725839Speter	    }
127825839Speter	}
127925839Speter
128025839Speter	nr->next = NULL;
128125839Speter	*pr = nr;
128225839Speter	pr = &nr->next;
128325839Speter    }
128425839Speter
128525839Speter    /* If the default branch was requested, add a revlist entry for
128625839Speter       it.  This is how rlog handles this option.  */
128725839Speter    if (default_branch
128825839Speter	&& (rcs->head != NULL || rcs->branch != NULL))
128925839Speter    {
129025839Speter	struct revlist *nr;
129125839Speter
129225839Speter	nr = (struct revlist *) xmalloc (sizeof *nr);
129325839Speter	if (rcs->branch != NULL)
129425839Speter	    nr->first = xstrdup (rcs->branch);
129525839Speter	else
129625839Speter	{
129725839Speter	    char *cp;
129825839Speter
129925839Speter	    nr->first = xstrdup (rcs->head);
1300175273Sobrien	    assert (nr->first);
130125839Speter	    cp = strrchr (nr->first, '.');
1302175273Sobrien	    assert (cp);
130325839Speter	    *cp = '\0';
130425839Speter	}
130525839Speter	nr->last = xstrdup (nr->first);
130625839Speter	nr->fields = numdots (nr->first) + 1;
130781404Speter	nr->inclusive = 1;
130825839Speter
130925839Speter	nr->next = NULL;
131025839Speter	*pr = nr;
131125839Speter    }
131225839Speter
131325839Speter    return ret;
131425839Speter}
131525839Speter
131625839Speter/*
131725839Speter * Free a revlist created by log_expand_revlist.
131825839Speter */
131925839Speterstatic void
132025839Speterlog_free_revlist (revlist)
132125839Speter    struct revlist *revlist;
132225839Speter{
132325839Speter    struct revlist *r;
132425839Speter
132525839Speter    r = revlist;
132625839Speter    while (r != NULL)
132725839Speter    {
132825839Speter	struct revlist *next;
132925839Speter
133025839Speter	if (r->first != NULL)
133125839Speter	    free (r->first);
133225839Speter	if (r->last != NULL)
133325839Speter	    free (r->last);
133425839Speter	next = r->next;
133525839Speter	free (r);
133625839Speter	r = next;
133725839Speter    }
133825839Speter}
133925839Speter
134025839Speter/*
134125839Speter * Return nonzero if a revision should be printed, based on the
134225839Speter * options provided.
134325839Speter */
134425839Speterstatic int
134525839Speterlog_version_requested (log_data, revlist, rcs, vnode)
134625839Speter    struct log_data *log_data;
134725839Speter    struct revlist *revlist;
134825839Speter    RCSNode *rcs;
134925839Speter    RCSVers *vnode;
135025839Speter{
135125839Speter    /* Handle the list of states from the -s option.  */
135225839Speter    if (log_data->statelist != NULL
135325839Speter	&& findnode (log_data->statelist, vnode->state) == NULL)
135425839Speter    {
135525839Speter	return 0;
135625839Speter    }
135725839Speter
135825839Speter    /* Handle the list of authors from the -w option.  */
135925839Speter    if (log_data->authorlist != NULL)
136025839Speter    {
136125839Speter	if (vnode->author != NULL
136225839Speter	    && findnode (log_data->authorlist, vnode->author) == NULL)
136325839Speter	{
136425839Speter	    return 0;
136525839Speter	}
136625839Speter    }
136725839Speter
136825839Speter    /* rlog considers all the -d options together when it decides
136925839Speter       whether to print a revision, so we must be compatible.  */
137025839Speter    if (log_data->datelist != NULL || log_data->singledatelist != NULL)
137125839Speter    {
137225839Speter	struct datelist *d;
137325839Speter
137425839Speter	for (d = log_data->datelist; d != NULL; d = d->next)
137525839Speter	{
137625839Speter	    int cmp;
137725839Speter
137825839Speter	    cmp = RCS_datecmp (vnode->date, d->start);
137925839Speter	    if (cmp > 0 || (cmp == 0 && d->inclusive))
138025839Speter	    {
138125839Speter		cmp = RCS_datecmp (vnode->date, d->end);
138225839Speter		if (cmp < 0 || (cmp == 0 && d->inclusive))
138325839Speter		    break;
138425839Speter	    }
138525839Speter	}
138625839Speter
138725839Speter	if (d == NULL)
138825839Speter	{
138925839Speter	    /* Look through the list of specific dates.  We want to
139025839Speter	       select the revision with the exact date found in the
139125839Speter	       start field.  The commit code ensures that it is
139225839Speter	       impossible to check in multiple revisions of a single
139325839Speter	       file in a single second, so checking the date this way
139425839Speter	       should never select more than one revision.  */
139525839Speter	    for (d = log_data->singledatelist; d != NULL; d = d->next)
139625839Speter	    {
139725839Speter		if (d->start != NULL
139825839Speter		    && RCS_datecmp (vnode->date, d->start) == 0)
139925839Speter		{
140025839Speter		    break;
140125839Speter		}
140225839Speter	    }
140325839Speter
140425839Speter	    if (d == NULL)
140525839Speter		return 0;
140625839Speter	}
140725839Speter    }
140825839Speter
140925839Speter    /* If the -r or -b options were used, REVLIST will be non NULL,
141025839Speter       and we print the union of the specified revisions.  */
141125839Speter    if (revlist != NULL)
141225839Speter    {
141325839Speter	char *v;
141425839Speter	int vfields;
141525839Speter	struct revlist *r;
141625839Speter
141725839Speter	/* This code is taken from rlog.  */
141825839Speter	v = vnode->version;
141925839Speter	vfields = numdots (v) + 1;
142025839Speter	for (r = revlist; r != NULL; r = r->next)
142125839Speter	{
142281404Speter	    if (vfields == r->fields + (r->fields & 1) &&
1423102840Speter		(r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1424102840Speter				version_compare (v, r->first, r->fields) > 0)
1425102840Speter		    && version_compare (v, r->last, r->fields) <= 0)
142625839Speter	    {
142725839Speter		return 1;
142825839Speter	    }
142925839Speter	}
143025839Speter
143125839Speter	/* If we get here, then the -b and/or the -r option was used,
143225839Speter           but did not match this revision, so we reject it.  */
143325839Speter
143425839Speter	return 0;
143525839Speter    }
143625839Speter
143725839Speter    /* By default, we print all revisions.  */
143825839Speter    return 1;
143925839Speter}
144025839Speter
1441128266Speter
1442128266Speter
144325839Speter/*
144425839Speter * Output a single symbol.  This is called via walklist.
144525839Speter */
144625839Speter/*ARGSUSED*/
144725839Speterstatic int
144825839Speterlog_symbol (p, closure)
144925839Speter    Node *p;
145025839Speter    void *closure;
145125839Speter{
145225839Speter    cvs_output ("\n\t", 2);
145325839Speter    cvs_output (p->key, 0);
145425839Speter    cvs_output (": ", 2);
145525839Speter    cvs_output (p->data, 0);
145625839Speter    return 0;
145725839Speter}
145825839Speter
1459128266Speter
1460128266Speter
146125839Speter/*
146225839Speter * Count the number of entries on a list.  This is called via walklist.
146325839Speter */
146425839Speter/*ARGSUSED*/
146525839Speterstatic int
146625839Speterlog_count (p, closure)
146725839Speter    Node *p;
146825839Speter    void *closure;
146925839Speter{
147025839Speter    return 1;
147125839Speter}
147225839Speter
1473128266Speter
1474128266Speter
147525839Speter/*
147625839Speter * Sort out a single date specification by narrowing down the date
147725839Speter * until we find the specific selected revision.
147825839Speter */
147925839Speterstatic int
148025839Speterlog_fix_singledate (p, closure)
148125839Speter    Node *p;
148225839Speter    void *closure;
148325839Speter{
148425839Speter    struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
148525839Speter    Node *pv;
148625839Speter    RCSVers *vnode;
148725839Speter    struct datelist *holdsingle, *holddate;
148825839Speter    int requested;
148925839Speter
149025839Speter    pv = findnode (data->rcs->versions, p->key);
149125839Speter    if (pv == NULL)
149225839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
149325839Speter	       p->key, data->rcs->path);
1494128266Speter    vnode = pv->data;
149525839Speter
149625839Speter    /* We are only interested if this revision passes any other tests.
149725839Speter       Temporarily clear log_data->singledatelist to avoid confusing
149825839Speter       log_version_requested.  We also clear log_data->datelist,
149925839Speter       because rlog considers all the -d options together.  We don't
150025839Speter       want to reject a revision because it does not match a date pair
150125839Speter       if we are going to select it on the basis of the singledate.  */
150225839Speter    holdsingle = data->log_data->singledatelist;
150325839Speter    data->log_data->singledatelist = NULL;
150425839Speter    holddate = data->log_data->datelist;
150525839Speter    data->log_data->datelist = NULL;
150625839Speter    requested = log_version_requested (data->log_data, data->revlist,
150725839Speter				       data->rcs, vnode);
150825839Speter    data->log_data->singledatelist = holdsingle;
150925839Speter    data->log_data->datelist = holddate;
151025839Speter
151125839Speter    if (requested)
151225839Speter    {
151325839Speter	struct datelist *d;
151425839Speter
151525839Speter	/* For each single date, if this revision is before the
151625839Speter	   specified date, but is closer than the previously selected
151725839Speter	   revision, select it instead.  */
151825839Speter	for (d = data->log_data->singledatelist; d != NULL; d = d->next)
151925839Speter	{
152025839Speter	    if (RCS_datecmp (vnode->date, d->end) <= 0
152125839Speter		&& (d->start == NULL
152225839Speter		    || RCS_datecmp (vnode->date, d->start) > 0))
152325839Speter	    {
152425839Speter		if (d->start != NULL)
152525839Speter		    free (d->start);
152625839Speter		d->start = xstrdup (vnode->date);
152725839Speter	    }
152825839Speter	}
152925839Speter    }
153025839Speter
153125839Speter    return 0;
153225839Speter}
153325839Speter
1534128266Speter
1535128266Speter
153625839Speter/*
153725839Speter * Count the number of revisions we are going to print.
153825839Speter */
153925839Speterstatic int
154025839Speterlog_count_print (p, closure)
154125839Speter    Node *p;
154225839Speter    void *closure;
154325839Speter{
154425839Speter    struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
154525839Speter    Node *pv;
154625839Speter
154725839Speter    pv = findnode (data->rcs->versions, p->key);
154825839Speter    if (pv == NULL)
154925839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
155025839Speter	       p->key, data->rcs->path);
155125839Speter    if (log_version_requested (data->log_data, data->revlist, data->rcs,
1552128266Speter			       pv->data))
155325839Speter	return 1;
155425839Speter    else
155525839Speter	return 0;
155625839Speter}
155725839Speter
155825839Speter/*
155925839Speter * Print the list of changes, not including the trunk, in reverse
156025839Speter * order for each branch.
156125839Speter */
156225839Speterstatic void
156325839Speterlog_tree (log_data, revlist, rcs, ver)
156425839Speter    struct log_data *log_data;
156525839Speter    struct revlist *revlist;
156625839Speter    RCSNode *rcs;
156725839Speter    const char *ver;
156825839Speter{
156925839Speter    Node *p;
157025839Speter    RCSVers *vnode;
157125839Speter
157225839Speter    p = findnode (rcs->versions, ver);
157325839Speter    if (p == NULL)
157425839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
157525839Speter	       ver, rcs->path);
1576128266Speter    vnode = p->data;
157725839Speter    if (vnode->next != NULL)
157825839Speter	log_tree (log_data, revlist, rcs, vnode->next);
157925839Speter    if (vnode->branches != NULL)
158025839Speter    {
158125839Speter	Node *head, *branch;
158225839Speter
158325839Speter	/* We need to do the branches in reverse order.  This breaks
158425839Speter           the List abstraction, but so does most of the branch
158525839Speter           manipulation in rcs.c.  */
158625839Speter	head = vnode->branches->list;
158725839Speter	for (branch = head->prev; branch != head; branch = branch->prev)
158825839Speter	{
158925839Speter	    log_abranch (log_data, revlist, rcs, branch->key);
159025839Speter	    log_tree (log_data, revlist, rcs, branch->key);
159125839Speter	}
159225839Speter    }
159325839Speter}
159425839Speter
159525839Speter/*
159625839Speter * Log the changes for a branch, in reverse order.
159725839Speter */
159825839Speterstatic void
159925839Speterlog_abranch (log_data, revlist, rcs, ver)
160025839Speter    struct log_data *log_data;
160125839Speter    struct revlist *revlist;
160225839Speter    RCSNode *rcs;
160325839Speter    const char *ver;
160425839Speter{
160525839Speter    Node *p;
160625839Speter    RCSVers *vnode;
160725839Speter
160825839Speter    p = findnode (rcs->versions, ver);
160925839Speter    if (p == NULL)
161025839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
161125839Speter	       ver, rcs->path);
1612128266Speter    vnode = p->data;
161325839Speter    if (vnode->next != NULL)
161425839Speter	log_abranch (log_data, revlist, rcs, vnode->next);
161525839Speter    log_version (log_data, revlist, rcs, vnode, 0);
161625839Speter}
161725839Speter
161825839Speter/*
161925839Speter * Print the log output for a single version.
162025839Speter */
162125839Speterstatic void
162225839Speterlog_version (log_data, revlist, rcs, ver, trunk)
162325839Speter    struct log_data *log_data;
162425839Speter    struct revlist *revlist;
162525839Speter    RCSNode *rcs;
162625839Speter    RCSVers *ver;
162725839Speter    int trunk;
162825839Speter{
162925839Speter    Node *p;
163025839Speter    int year, mon, mday, hour, min, sec;
163125839Speter    char buf[100];
163225839Speter    Node *padd, *pdel;
163325839Speter
163425839Speter    if (! log_version_requested (log_data, revlist, rcs, ver))
163525839Speter	return;
163625839Speter
163725839Speter    cvs_output ("----------------------------\nrevision ", 0);
163825839Speter    cvs_output (ver->version, 0);
163925839Speter
164032785Speter    p = findnode (RCS_getlocks (rcs), ver->version);
164125839Speter    if (p != NULL)
164225839Speter    {
164325839Speter	cvs_output ("\tlocked by: ", 0);
164425839Speter	cvs_output (p->data, 0);
164525839Speter	cvs_output (";", 1);
164625839Speter    }
164725839Speter
164825839Speter    cvs_output ("\ndate: ", 0);
164925839Speter    (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
165025839Speter		   &sec);
165125839Speter    if (year < 1900)
165225839Speter	year += 1900;
1653131688Sdes    sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d",
1654131688Sdes	     year, datesep, mon, datesep, mday, hour, min, sec);
165525839Speter    cvs_output (buf, 0);
165625839Speter
165725839Speter    cvs_output (";  author: ", 0);
165825839Speter    cvs_output (ver->author, 0);
165925839Speter
166025839Speter    cvs_output (";  state: ", 0);
166125839Speter    cvs_output (ver->state, 0);
166225839Speter    cvs_output (";", 1);
166325839Speter
166425839Speter    if (! trunk)
166525839Speter    {
166625839Speter	padd = findnode (ver->other, ";add");
166725839Speter	pdel = findnode (ver->other, ";delete");
166825839Speter    }
166925839Speter    else if (ver->next == NULL)
167025839Speter    {
167125839Speter	padd = NULL;
167225839Speter	pdel = NULL;
167325839Speter    }
167425839Speter    else
167525839Speter    {
167625839Speter	Node *nextp;
167725839Speter	RCSVers *nextver;
167825839Speter
167925839Speter	nextp = findnode (rcs->versions, ver->next);
168025839Speter	if (nextp == NULL)
168125839Speter	    error (1, 0, "missing version `%s' in `%s'", ver->next,
168225839Speter		   rcs->path);
1683128266Speter	nextver = nextp->data;
168425839Speter	pdel = findnode (nextver->other, ";add");
168525839Speter	padd = findnode (nextver->other, ";delete");
168625839Speter    }
168725839Speter
168825839Speter    if (padd != NULL)
168925839Speter    {
1690175273Sobrien	assert (pdel);
169125839Speter	cvs_output ("  lines: +", 0);
169225839Speter	cvs_output (padd->data, 0);
169325839Speter	cvs_output (" -", 2);
169425839Speter	cvs_output (pdel->data, 0);
169525839Speter    }
169625839Speter
169725839Speter    if (ver->branches != NULL)
169825839Speter    {
169925839Speter	cvs_output ("\nbranches:", 0);
170025839Speter	walklist (ver->branches, log_branch, (void *) NULL);
170125839Speter    }
170225839Speter
170325839Speter    cvs_output ("\n", 1);
170425839Speter
170525839Speter    p = findnode (ver->other, "log");
170632785Speter    /* The p->date == NULL case is the normal one for an empty log
170732785Speter       message (rcs-14 in sanity.sh).  I don't think the case where
170832785Speter       p->data is "" can happen (getrcskey in rcs.c checks for an
170932785Speter       empty string and set the value to NULL in that case).  My guess
171032785Speter       would be the p == NULL case would mean an RCS file which was
171132785Speter       missing the "log" keyword (which is illegal according to
171232785Speter       rcsfile.5).  */
1713128266Speter    if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
171425839Speter	cvs_output ("*** empty log message ***\n", 0);
171525839Speter    else
171625839Speter    {
171725839Speter	/* FIXME: Technically, the log message could contain a null
171825839Speter           byte.  */
171925839Speter	cvs_output (p->data, 0);
1720128266Speter	if (((char *)p->data)[strlen (p->data) - 1] != '\n')
172125839Speter	    cvs_output ("\n", 1);
172225839Speter    }
172325839Speter}
172425839Speter
172525839Speter/*
172625839Speter * Output a branch version.  This is called via walklist.
172725839Speter */
172825839Speter/*ARGSUSED*/
172925839Speterstatic int
173025839Speterlog_branch (p, closure)
173125839Speter    Node *p;
173225839Speter    void *closure;
173325839Speter{
173425839Speter    cvs_output ("  ", 2);
173525839Speter    if ((numdots (p->key) & 1) == 0)
173625839Speter	cvs_output (p->key, 0);
173725839Speter    else
173825839Speter    {
173925839Speter	char *f, *cp;
174025839Speter
174125839Speter	f = xstrdup (p->key);
174225839Speter	cp = strrchr (f, '.');
174325839Speter	*cp = '\0';
174425839Speter	cvs_output (f, 0);
174525839Speter	free (f);
174625839Speter    }
174725839Speter    cvs_output (";", 1);
174825839Speter    return 0;
174925839Speter}
175025839Speter
175125839Speter/*
175217721Speter * Print a warm fuzzy message
175317721Speter */
175417721Speter/* ARGSUSED */
175517721Speterstatic Dtype
175625839Speterlog_dirproc (callerdat, dir, repository, update_dir, entries)
175725839Speter    void *callerdat;
1758128266Speter    const char *dir;
1759128266Speter    const char *repository;
1760128266Speter    const char *update_dir;
176125839Speter    List *entries;
176217721Speter{
176317721Speter    if (!isdir (dir))
176417721Speter	return (R_SKIP_ALL);
176517721Speter
176617721Speter    if (!quiet)
176717721Speter	error (0, 0, "Logging %s", update_dir);
176817721Speter    return (R_PROCESS);
176917721Speter}
177025839Speter
177125839Speter/*
177225839Speter * Compare versions.  This is taken from RCS compartial.
177325839Speter */
177425839Speterstatic int
177525839Speterversion_compare (v1, v2, len)
177625839Speter    const char *v1;
177725839Speter    const char *v2;
177825839Speter    int len;
177925839Speter{
178025839Speter    while (1)
178125839Speter    {
178225839Speter	int d1, d2, r;
178325839Speter
178425839Speter	if (*v1 == '\0')
178525839Speter	    return 1;
178625839Speter	if (*v2 == '\0')
178725839Speter	    return -1;
178825839Speter
178925839Speter	while (*v1 == '0')
179025839Speter	    ++v1;
179154427Speter	for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
179225839Speter	    ;
179325839Speter
179425839Speter	while (*v2 == '0')
179525839Speter	    ++v2;
179654427Speter	for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
179725839Speter	    ;
179825839Speter
179925839Speter	if (d1 != d2)
180025839Speter	    return d1 < d2 ? -1 : 1;
180125839Speter
180225839Speter	r = memcmp (v1, v2, d1);
180325839Speter	if (r != 0)
180425839Speter	    return r;
180525839Speter
180625839Speter	--len;
180725839Speter	if (len == 0)
180825839Speter	    return 0;
180925839Speter
181025839Speter	v1 += d1;
181125839Speter	v2 += d1;
181225839Speter
181325839Speter	if (*v1 == '.')
181425839Speter	    ++v1;
181525839Speter	if (*v2 == '.')
181625839Speter	    ++v2;
181725839Speter    }
181825839Speter}
1819