log.c revision 128266
117721Speter/*
217721Speter * Copyright (c) 1992, Brian Berliner and Jeff Polk
317721Speter * Copyright (c) 1989-1992, Brian Berliner
417721Speter *
517721Speter * You may distribute under the terms of the GNU General Public License as
632785Speter * specified in the README file that comes with the CVS source distribution.
717721Speter *
817721Speter * Print Log Information
917721Speter *
1017721Speter * Prints the RCS "log" (rlog) information for the specified files.  With no
1117721Speter * argument, prints the log information for all the files in the directory
1217721Speter * (recursive by default).
1317721Speter */
1417721Speter
1517721Speter#include "cvs.h"
1617721Speter
1725839Speter/* This structure holds information parsed from the -r option.  */
1817721Speter
1925839Speterstruct option_revlist
2025839Speter{
2125839Speter    /* The next -r option.  */
2225839Speter    struct option_revlist *next;
2325839Speter    /* The first revision to print.  This is NULL if the range is
2425839Speter       :rev, or if no revision is given.  */
2525839Speter    char *first;
2625839Speter    /* The last revision to print.  This is NULL if the range is rev:,
2725839Speter       or if no revision is given.  If there is no colon, first and
2825839Speter       last are the same.  */
2925839Speter    char *last;
3025839Speter    /* Nonzero if there was a trailing `.', which means to print only
3125839Speter       the head revision of a branch.  */
3225839Speter    int branchhead;
3381404Speter    /* Nonzero if first and last are inclusive.  */
3481404Speter    int inclusive;
3525839Speter};
3625839Speter
3725839Speter/* This structure holds information derived from option_revlist given
3825839Speter   a particular RCS file.  */
3925839Speter
4025839Speterstruct revlist
4125839Speter{
4225839Speter    /* The next pair.  */
4325839Speter    struct revlist *next;
4425839Speter    /* The first numeric revision to print.  */
4525839Speter    char *first;
4625839Speter    /* The last numeric revision to print.  */
4725839Speter    char *last;
4825839Speter    /* The number of fields in these revisions (one more than
4925839Speter       numdots).  */
5025839Speter    int fields;
5181404Speter    /* Whether first & last are to be included or excluded.  */
5281404Speter    int inclusive;
5325839Speter};
5425839Speter
5525839Speter/* This structure holds information parsed from the -d option.  */
5625839Speter
5725839Speterstruct datelist
5825839Speter{
5925839Speter    /* The next date.  */
6025839Speter    struct datelist *next;
6125839Speter    /* The starting date.  */
6225839Speter    char *start;
6325839Speter    /* The ending date.  */
6425839Speter    char *end;
6525839Speter    /* Nonzero if the range is inclusive rather than exclusive.  */
6625839Speter    int inclusive;
6725839Speter};
6825839Speter
6925839Speter/* This structure is used to pass information through start_recursion.  */
7025839Speterstruct log_data
7125839Speter{
7225839Speter    /* Nonzero if the -R option was given, meaning that only the name
7325839Speter       of the RCS file should be printed.  */
7425839Speter    int nameonly;
7525839Speter    /* Nonzero if the -h option was given, meaning that only header
7625839Speter       information should be printed.  */
7725839Speter    int header;
7825839Speter    /* Nonzero if the -t option was given, meaning that only the
7925839Speter       header and the descriptive text should be printed.  */
8025839Speter    int long_header;
8125839Speter    /* Nonzero if the -N option was seen, meaning that tag information
8225839Speter       should not be printed.  */
8325839Speter    int notags;
8425839Speter    /* Nonzero if the -b option was seen, meaning that only revisions
8525839Speter       on the default branch should be printed.  */
8625839Speter    int default_branch;
87102840Speter    /* Nonzero if the -S option was seen, meaning that the header/name
88102840Speter       should be suppressed if no revisions are selected.  */
89102840Speter    int sup_header;
9025839Speter    /* If not NULL, the value given for the -r option, which lists
9125839Speter       sets of revisions to be printed.  */
9225839Speter    struct option_revlist *revlist;
9325839Speter    /* If not NULL, the date pairs given for the -d option, which
9425839Speter       select date ranges to print.  */
9525839Speter    struct datelist *datelist;
9625839Speter    /* If not NULL, the single dates given for the -d option, which
9725839Speter       select specific revisions to print based on a date.  */
9825839Speter    struct datelist *singledatelist;
9925839Speter    /* If not NULL, the list of states given for the -s option, which
10025839Speter       only prints revisions of given states.  */
10125839Speter    List *statelist;
10225839Speter    /* If not NULL, the list of login names given for the -w option,
10325839Speter       which only prints revisions checked in by given users.  */
10425839Speter    List *authorlist;
10525839Speter};
10625839Speter
10725839Speter/* This structure is used to pass information through walklist.  */
10825839Speterstruct log_data_and_rcs
10925839Speter{
11025839Speter    struct log_data *log_data;
11125839Speter    struct revlist *revlist;
11225839Speter    RCSNode *rcs;
11325839Speter};
11425839Speter
11581404Speterstatic int rlog_proc PROTO((int argc, char **argv, char *xwhere,
11681404Speter			    char *mwhere, char *mfile, int shorten,
11781404Speter			    int local_specified, char *mname, char *msg));
118128266Speterstatic Dtype log_dirproc PROTO ((void *callerdat, const char *dir,
119128266Speter                                 const char *repository,
120128266Speter                                 const char *update_dir,
121128266Speter                                 List *entries));
12225839Speterstatic int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
12325839Speterstatic struct option_revlist *log_parse_revlist PROTO ((const char *));
12425839Speterstatic void log_parse_date PROTO ((struct log_data *, const char *));
12525839Speterstatic void log_parse_list PROTO ((List **, const char *));
12625839Speterstatic struct revlist *log_expand_revlist PROTO ((RCSNode *,
12725839Speter						  struct option_revlist *,
12825839Speter						  int));
12925839Speterstatic void log_free_revlist PROTO ((struct revlist *));
13025839Speterstatic int log_version_requested PROTO ((struct log_data *, struct revlist *,
13125839Speter					 RCSNode *, RCSVers *));
13225839Speterstatic int log_symbol PROTO ((Node *, void *));
13325839Speterstatic int log_count PROTO ((Node *, void *));
13425839Speterstatic int log_fix_singledate PROTO ((Node *, void *));
13525839Speterstatic int log_count_print PROTO ((Node *, void *));
13625839Speterstatic void log_tree PROTO ((struct log_data *, struct revlist *,
13725839Speter			     RCSNode *, const char *));
13825839Speterstatic void log_abranch PROTO ((struct log_data *, struct revlist *,
13925839Speter				RCSNode *, const char *));
14025839Speterstatic void log_version PROTO ((struct log_data *, struct revlist *,
14125839Speter				RCSNode *, RCSVers *, int));
14225839Speterstatic int log_branch PROTO ((Node *, void *));
14325839Speterstatic int version_compare PROTO ((const char *, const char *, int));
14425839Speter
14581404Speterstatic struct log_data log_data;
14681404Speterstatic int is_rlog;
14781404Speter
14817721Speterstatic const char *const log_usage[] =
14917721Speter{
15025839Speter    "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
15125839Speter    "    [-w[logins]] [files...]\n",
15217721Speter    "\t-l\tLocal directory only, no recursion.\n",
15325839Speter    "\t-R\tOnly print name of RCS file.\n",
15425839Speter    "\t-h\tOnly print header.\n",
15525839Speter    "\t-t\tOnly print header and descriptive text.\n",
15625839Speter    "\t-N\tDo not list tags.\n",
157102840Speter    "\t-S\tDo not print name/header if no revisions selected.\n",
15825839Speter    "\t-b\tOnly list revisions on the default branch.\n",
159102840Speter    "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
16081404Speter    "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
161102840Speter    "\t   rev1::rev2  Between rev1 and rev2, excluding rev1.\n",
16281404Speter    "\t   rev:        rev and following revisions on the same branch.\n",
16381404Speter    "\t   rev::       After rev on the same branch.\n",
16481404Speter    "\t   :rev        rev and previous revisions on the same branch.\n",
165102840Speter    "\t   ::rev       rev and previous revisions on the same branch.\n",
16681404Speter    "\t   rev         Just rev.\n",
16781404Speter    "\t   branch      All revisions on the branch.\n",
16881404Speter    "\t   branch.     The last revision on the branch.\n",
169102840Speter    "\t-d dates\tA semicolon-separated list of dates\n",
170102840Speter    "\t        \t(D1<D2 for range, D for latest before).\n",
17125839Speter    "\t-s states\tOnly list revisions with specified states.\n",
17225839Speter    "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
17332785Speter    "(Specify the --help global option for a list of other help options)\n",
17417721Speter    NULL
17517721Speter};
17617721Speter
17766525Speter#ifdef CLIENT_SUPPORT
17866525Speter
17966525Speter/* Helper function for send_arg_list.  */
18066525Speterstatic int send_one PROTO ((Node *, void *));
18166525Speter
18266525Speterstatic int
18366525Spetersend_one (node, closure)
18466525Speter    Node *node;
18566525Speter    void *closure;
18666525Speter{
18766525Speter    char *option = (char *) closure;
18866525Speter
18966525Speter    send_to_server ("Argument ", 0);
19066525Speter    send_to_server (option, 0);
19166525Speter    if (strcmp (node->key, "@@MYSELF") == 0)
19266525Speter	/* It is a bare -w option.  Note that we must send it as
19366525Speter	   -w rather than messing with getcaller() or something (which on
19466525Speter	   the client will return garbage).  */
19566525Speter	;
19666525Speter    else
19766525Speter	send_to_server (node->key, 0);
19866525Speter    send_to_server ("\012", 0);
19966525Speter    return 0;
20066525Speter}
20166525Speter
20266525Speter/* For each element in ARG, send an argument consisting of OPTION
20366525Speter   concatenated with that element.  */
20466525Speterstatic void send_arg_list PROTO ((char *, List *));
20566525Speter
20666525Speterstatic void
20766525Spetersend_arg_list (option, arg)
20866525Speter    char *option;
20966525Speter    List *arg;
21066525Speter{
21166525Speter    if (arg == NULL)
21266525Speter	return;
21366525Speter    walklist (arg, send_one, (void *)option);
21466525Speter}
21566525Speter
21666525Speter#endif
21766525Speter
21817721Speterint
21917721Spetercvslog (argc, argv)
22017721Speter    int argc;
22117721Speter    char **argv;
22217721Speter{
22325839Speter    int c;
22417721Speter    int err = 0;
22517721Speter    int local = 0;
22666525Speter    struct option_revlist **prl;
22717721Speter
228128266Speter    is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
22981404Speter
23017721Speter    if (argc == -1)
23117721Speter	usage (log_usage);
23217721Speter
23325839Speter    memset (&log_data, 0, sizeof log_data);
23466525Speter    prl = &log_data.revlist;
23517721Speter
23626065Speter    optind = 0;
237102840Speter    while ((c = getopt (argc, argv, "+bd:hlNSRr::s:tw::")) != -1)
23825839Speter    {
23925839Speter	switch (c)
24025839Speter	{
24125839Speter	    case 'b':
24225839Speter		log_data.default_branch = 1;
24325839Speter		break;
24425839Speter	    case 'd':
24525839Speter		log_parse_date (&log_data, optarg);
24625839Speter		break;
24725839Speter	    case 'h':
24825839Speter		log_data.header = 1;
24925839Speter		break;
25025839Speter	    case 'l':
25125839Speter		local = 1;
25225839Speter		break;
25325839Speter	    case 'N':
25425839Speter		log_data.notags = 1;
25525839Speter		break;
256102840Speter	    case 'S':
257102840Speter		log_data.sup_header = 1;
258102840Speter		break;
25925839Speter	    case 'R':
26025839Speter		log_data.nameonly = 1;
26125839Speter		break;
26225839Speter	    case 'r':
26366525Speter		*prl = log_parse_revlist (optarg);
26466525Speter		prl = &(*prl)->next;
26525839Speter		break;
26625839Speter	    case 's':
26725839Speter		log_parse_list (&log_data.statelist, optarg);
26825839Speter		break;
26925839Speter	    case 't':
27025839Speter		log_data.long_header = 1;
27125839Speter		break;
27225839Speter	    case 'w':
27325839Speter		if (optarg != NULL)
27425839Speter		    log_parse_list (&log_data.authorlist, optarg);
27525839Speter		else
27666525Speter		    log_parse_list (&log_data.authorlist, "@@MYSELF");
27725839Speter		break;
27825839Speter	    case '?':
27925839Speter	    default:
28025839Speter		usage (log_usage);
28125839Speter		break;
28225839Speter	}
28325839Speter    }
28481404Speter    argc -= optind;
28581404Speter    argv += optind;
28625839Speter
28717721Speter    wrap_setup ();
28817721Speter
28917721Speter#ifdef CLIENT_SUPPORT
29081404Speter    if (current_parsed_root->isremote)
29125839Speter    {
29266525Speter	struct datelist *p;
29366525Speter	struct option_revlist *rp;
29466525Speter	char datetmp[MAXDATELEN];
29525839Speter
29617721Speter	/* We're the local client.  Fire up the remote server.  */
29717721Speter	start_server ();
29881404Speter
29981404Speter	if (is_rlog && !supported_request ("rlog"))
30081404Speter	    error (1, 0, "server does not support rlog");
30181404Speter
30217721Speter	ign_setup ();
30317721Speter
30466525Speter	if (log_data.default_branch)
30566525Speter	    send_arg ("-b");
30617721Speter
30766525Speter	while (log_data.datelist != NULL)
30866525Speter	{
30966525Speter	    p = log_data.datelist;
31066525Speter	    log_data.datelist = p->next;
31166525Speter	    send_to_server ("Argument -d\012", 0);
31266525Speter	    send_to_server ("Argument ", 0);
31366525Speter	    date_to_internet (datetmp, p->start);
31466525Speter	    send_to_server (datetmp, 0);
31566525Speter	    if (p->inclusive)
31666525Speter		send_to_server ("<=", 0);
31766525Speter	    else
31866525Speter		send_to_server ("<", 0);
31966525Speter	    date_to_internet (datetmp, p->end);
32066525Speter	    send_to_server (datetmp, 0);
32166525Speter	    send_to_server ("\012", 0);
32266525Speter	    if (p->start)
32366525Speter		free (p->start);
32466525Speter	    if (p->end)
32566525Speter		free (p->end);
32666525Speter	    free (p);
32766525Speter	}
32866525Speter	while (log_data.singledatelist != NULL)
32966525Speter	{
33066525Speter	    p = log_data.singledatelist;
33166525Speter	    log_data.singledatelist = p->next;
33266525Speter	    send_to_server ("Argument -d\012", 0);
33366525Speter	    send_to_server ("Argument ", 0);
33466525Speter	    date_to_internet (datetmp, p->end);
33566525Speter	    send_to_server (datetmp, 0);
33666525Speter	    send_to_server ("\012", 0);
33766525Speter	    if (p->end)
33866525Speter		free (p->end);
33966525Speter	    free (p);
34066525Speter	}
34166525Speter
34266525Speter	if (log_data.header)
34366525Speter	    send_arg ("-h");
34466525Speter	if (local)
34566525Speter	    send_arg("-l");
34666525Speter	if (log_data.notags)
34766525Speter	    send_arg("-N");
348102840Speter	if (log_data.sup_header)
349102840Speter	    send_arg("-S");
35066525Speter	if (log_data.nameonly)
35166525Speter	    send_arg("-R");
35266525Speter	if (log_data.long_header)
35366525Speter	    send_arg("-t");
35417721Speter
35566525Speter	while (log_data.revlist != NULL)
35666525Speter	{
35766525Speter	    rp = log_data.revlist;
35866525Speter	    log_data.revlist = rp->next;
35966525Speter	    send_to_server ("Argument -r", 0);
36066525Speter	    if (rp->branchhead)
36166525Speter	    {
36266525Speter		if (rp->first != NULL)
36366525Speter		    send_to_server (rp->first, 0);
36466525Speter		send_to_server (".", 1);
36566525Speter	    }
36666525Speter	    else
36766525Speter	    {
36866525Speter		if (rp->first != NULL)
36966525Speter		    send_to_server (rp->first, 0);
37066525Speter		send_to_server (":", 1);
37181404Speter		if (!rp->inclusive)
37281404Speter		    send_to_server (":", 1);
37366525Speter		if (rp->last != NULL)
37466525Speter		    send_to_server (rp->last, 0);
37566525Speter	    }
37666525Speter	    send_to_server ("\012", 0);
37766525Speter	    if (rp->first)
37866525Speter		free (rp->first);
37966525Speter	    if (rp->last)
38066525Speter		free (rp->last);
38166525Speter	    free (rp);
38266525Speter	}
38366525Speter	send_arg_list ("-s", log_data.statelist);
38466525Speter	dellist (&log_data.statelist);
38566525Speter	send_arg_list ("-w", log_data.authorlist);
38666525Speter	dellist (&log_data.authorlist);
387107484Speter	send_arg ("--");
38866525Speter
38981404Speter	if (is_rlog)
39081404Speter	{
39181404Speter	    int i;
39281404Speter	    for (i = 0; i < argc; i++)
39381404Speter		send_arg (argv[i]);
39481404Speter	    send_to_server ("rlog\012", 0);
39581404Speter	}
39681404Speter	else
39781404Speter	{
39881404Speter	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
39981404Speter	    send_file_names (argc, argv, SEND_EXPAND_WILD);
40081404Speter	    send_to_server ("log\012", 0);
40181404Speter	}
40217721Speter        err = get_responses_and_close ();
40317721Speter	return err;
40417721Speter    }
40517721Speter#endif
40617721Speter
40766525Speter    /* OK, now that we know we are local/server, we can resolve @@MYSELF
40866525Speter       into our user name.  */
40966525Speter    if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
41066525Speter	log_parse_list (&log_data.authorlist, getcaller ());
41166525Speter
41281404Speter    if (is_rlog)
41381404Speter    {
41481404Speter	DBM *db;
41581404Speter	int i;
41681404Speter	db = open_module ();
41781404Speter	for (i = 0; i < argc; i++)
41881404Speter	{
41981404Speter	    err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
420102840Speter			     (char *) NULL, 0, local, 0, 0, (char *) NULL);
42181404Speter	}
42281404Speter	close_module (db);
42381404Speter    }
42481404Speter    else
42581404Speter    {
42681404Speter	err = rlog_proc (argc + 1, argv - 1, (char *) NULL,
427102840Speter			 (char *) NULL, (char *) NULL, 0, local, (char *) NULL,
42881404Speter			 (char *) NULL);
42981404Speter    }
43066525Speter
43166525Speter    while (log_data.revlist)
43266525Speter    {
43366525Speter	struct option_revlist *rl = log_data.revlist->next;
43466525Speter	if (log_data.revlist->first)
43566525Speter	    free (log_data.revlist->first);
43666525Speter	if (log_data.revlist->last)
43766525Speter	    free (log_data.revlist->last);
43866525Speter	free (log_data.revlist);
43966525Speter	log_data.revlist = rl;
44066525Speter    }
44166525Speter    while (log_data.datelist)
44266525Speter    {
44366525Speter	struct datelist *nd = log_data.datelist->next;
44466525Speter	if (log_data.datelist->start)
44566525Speter	    free (log_data.datelist->start);
44666525Speter	if (log_data.datelist->end)
44766525Speter	    free (log_data.datelist->end);
44866525Speter	free (log_data.datelist);
44966525Speter	log_data.datelist = nd;
45066525Speter    }
45166525Speter    while (log_data.singledatelist)
45266525Speter    {
45366525Speter	struct datelist *nd = log_data.singledatelist->next;
45466525Speter	if (log_data.singledatelist->start)
45566525Speter	    free (log_data.singledatelist->start);
45666525Speter	if (log_data.singledatelist->end)
45766525Speter	    free (log_data.singledatelist->end);
45866525Speter	free (log_data.singledatelist);
45966525Speter	log_data.singledatelist = nd;
46066525Speter    }
46166525Speter    dellist (&log_data.statelist);
46266525Speter    dellist (&log_data.authorlist);
46366525Speter
46417721Speter    return (err);
46517721Speter}
46617721Speter
46781404Speter
46881404Speterstatic int
46981404Speterrlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
47081404Speter    int argc;
47181404Speter    char **argv;
47281404Speter    char *xwhere;
47381404Speter    char *mwhere;
47481404Speter    char *mfile;
47581404Speter    int shorten;
47681404Speter    int local;
47781404Speter    char *mname;
47881404Speter    char *msg;
47981404Speter{
48081404Speter    /* Begin section which is identical to patch_proc--should this
48181404Speter       be abstracted out somehow?  */
48281404Speter    char *myargv[2];
48381404Speter    int err = 0;
48481404Speter    int which;
48581404Speter    char *repository;
48681404Speter    char *where;
48781404Speter
48881404Speter    if (is_rlog)
48981404Speter    {
490128266Speter	repository = xmalloc (strlen (current_parsed_root->directory)
491128266Speter                              + strlen (argv[0])
49281404Speter			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
493128266Speter	(void)sprintf (repository, "%s/%s",
494128266Speter                       current_parsed_root->directory, argv[0]);
495128266Speter	where = xmalloc (strlen (argv[0])
496128266Speter                         + (mfile == NULL ? 0 : strlen (mfile) + 1)
49781404Speter			 + 1);
49881404Speter	(void) strcpy (where, argv[0]);
49981404Speter
500128266Speter	/* If mfile isn't null, we need to set up to do only part of theu
501128266Speter         * module.
502128266Speter         */
50381404Speter	if (mfile != NULL)
50481404Speter	{
50581404Speter	    char *cp;
50681404Speter	    char *path;
50781404Speter
508128266Speter	    /* If the portion of the module is a path, put the dir part on
509128266Speter             * repos.
510128266Speter             */
51181404Speter	    if ((cp = strrchr (mfile, '/')) != NULL)
51281404Speter	    {
51381404Speter		*cp = '\0';
514128266Speter		(void)strcat (repository, "/");
515128266Speter		(void)strcat (repository, mfile);
516128266Speter		(void)strcat (where, "/");
517128266Speter		(void)strcat (where, mfile);
51881404Speter		mfile = cp + 1;
51981404Speter	    }
52081404Speter
52181404Speter	    /* take care of the rest */
52281404Speter	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
523128266Speter	    (void)sprintf (path, "%s/%s", repository, mfile);
52481404Speter	    if (isdir (path))
52581404Speter	    {
52681404Speter		/* directory means repository gets the dir tacked on */
527128266Speter		(void)strcpy (repository, path);
528128266Speter		(void)strcat (where, "/");
529128266Speter		(void)strcat (where, mfile);
53081404Speter	    }
53181404Speter	    else
53281404Speter	    {
53381404Speter		myargv[0] = argv[0];
53481404Speter		myargv[1] = mfile;
53581404Speter		argc = 2;
53681404Speter		argv = myargv;
53781404Speter	    }
53881404Speter	    free (path);
53981404Speter	}
54081404Speter
54181404Speter	/* cd to the starting repository */
542128266Speter	if (CVS_CHDIR (repository) < 0)
54381404Speter	{
54481404Speter	    error (0, errno, "cannot chdir to %s", repository);
54581404Speter	    free (repository);
546128266Speter	    free (where);
547128266Speter	    return 1;
54881404Speter	}
54981404Speter	/* End section which is identical to patch_proc.  */
55081404Speter
55181404Speter	which = W_REPOS | W_ATTIC;
55281404Speter    }
55381404Speter    else
55481404Speter    {
555128266Speter        repository = NULL;
55681404Speter        where = NULL;
55781404Speter        which = W_LOCAL | W_REPOS | W_ATTIC;
55881404Speter    }
55981404Speter
56081404Speter    err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
56181404Speter			   (DIRLEAVEPROC) NULL, (void *) &log_data,
562109655Speter			   argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
563128266Speter			   where, 1, repository);
564128266Speter
565128266Speter    if (!(which & W_LOCAL)) free (repository);
566128266Speter    if (where) free (where);
567128266Speter
56881404Speter    return err;
56981404Speter}
57081404Speter
57181404Speter
572128266Speter
57325839Speter/*
57425839Speter * Parse a revision list specification.
57525839Speter */
57625839Speterstatic struct option_revlist *
57725839Speterlog_parse_revlist (argstring)
57825839Speter    const char *argstring;
57925839Speter{
58066525Speter    char *orig_copy, *copy;
58125839Speter    struct option_revlist *ret, **pr;
58225839Speter
58325839Speter    /* Unfortunately, rlog accepts -r without an argument to mean that
58425839Speter       latest revision on the default branch, so we must support that
58525839Speter       for compatibility.  */
58625839Speter    if (argstring == NULL)
58766525Speter	argstring = "";
58825839Speter
58925839Speter    ret = NULL;
59025839Speter    pr = &ret;
59125839Speter
59225839Speter    /* Copy the argument into memory so that we can change it.  We
59325839Speter       don't want to change the argument because, at least as of this
59466525Speter       writing, we will use it if we send the arguments to the server.  */
59566525Speter    orig_copy = copy = xstrdup (argstring);
59625839Speter    while (copy != NULL)
59725839Speter    {
59825839Speter	char *comma;
59925839Speter	struct option_revlist *r;
60025839Speter
60125839Speter	comma = strchr (copy, ',');
60225839Speter	if (comma != NULL)
60325839Speter	    *comma++ = '\0';
60425839Speter
60525839Speter	r = (struct option_revlist *) xmalloc (sizeof *r);
60625839Speter	r->next = NULL;
60766525Speter	r->first = copy;
60866525Speter	r->branchhead = 0;
60966525Speter	r->last = strchr (copy, ':');
61066525Speter	if (r->last != NULL)
61181404Speter	{
61266525Speter	    *r->last++ = '\0';
61381404Speter	    r->inclusive = (*r->last != ':');
61481404Speter	    if (!r->inclusive)
61581404Speter		r->last++;
61681404Speter	}
61725839Speter	else
61825839Speter	{
61966525Speter	    r->last = r->first;
62081404Speter	    r->inclusive = 1;
62166525Speter	    if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
62266525Speter	    {
62366525Speter		r->branchhead = 1;
62466525Speter		r->first[strlen (r->first) - 1] = '\0';
62566525Speter	    }
62625839Speter	}
62725839Speter
62866525Speter	if (*r->first == '\0')
62966525Speter	    r->first = NULL;
63066525Speter	if (*r->last == '\0')
63166525Speter	    r->last = NULL;
63266525Speter
63366525Speter	if (r->first != NULL)
63466525Speter	    r->first = xstrdup (r->first);
63566525Speter	if (r->last != NULL)
63666525Speter	    r->last = xstrdup (r->last);
63766525Speter
63825839Speter	*pr = r;
63925839Speter	pr = &r->next;
64025839Speter
64125839Speter	copy = comma;
64225839Speter    }
64325839Speter
64466525Speter    free (orig_copy);
64525839Speter    return ret;
64625839Speter}
64725839Speter
64817721Speter/*
64925839Speter * Parse a date specification.
65025839Speter */
65125839Speterstatic void
65225839Speterlog_parse_date (log_data, argstring)
65325839Speter    struct log_data *log_data;
65425839Speter    const char *argstring;
65525839Speter{
65625839Speter    char *orig_copy, *copy;
65725839Speter
65825839Speter    /* Copy the argument into memory so that we can change it.  We
65925839Speter       don't want to change the argument because, at least as of this
66025839Speter       writing, we will use it if we send the arguments to the server.  */
66166525Speter    orig_copy = copy = xstrdup (argstring);
66225839Speter    while (copy != NULL)
66325839Speter    {
66425839Speter	struct datelist *nd, **pd;
66525839Speter	char *cpend, *cp, *ds, *de;
66625839Speter
66725839Speter	nd = (struct datelist *) xmalloc (sizeof *nd);
66825839Speter
66925839Speter	cpend = strchr (copy, ';');
67025839Speter	if (cpend != NULL)
67125839Speter	    *cpend++ = '\0';
67225839Speter
67325839Speter	pd = &log_data->datelist;
67425839Speter	nd->inclusive = 0;
67525839Speter
67625839Speter	if ((cp = strchr (copy, '>')) != NULL)
67725839Speter	{
67825839Speter	    *cp++ = '\0';
67925839Speter	    if (*cp == '=')
68025839Speter	    {
68125839Speter		++cp;
68225839Speter		nd->inclusive = 1;
68325839Speter	    }
68425839Speter	    ds = cp;
68525839Speter	    de = copy;
68625839Speter	}
68725839Speter	else if ((cp = strchr (copy, '<')) != NULL)
68825839Speter	{
68925839Speter	    *cp++ = '\0';
69025839Speter	    if (*cp == '=')
69125839Speter	    {
69225839Speter		++cp;
69325839Speter		nd->inclusive = 1;
69425839Speter	    }
69525839Speter	    ds = copy;
69625839Speter	    de = cp;
69725839Speter	}
69825839Speter	else
69925839Speter	{
70025839Speter	    ds = NULL;
70125839Speter	    de = copy;
70225839Speter	    pd = &log_data->singledatelist;
70325839Speter	}
70425839Speter
70525839Speter	if (ds == NULL)
70625839Speter	    nd->start = NULL;
70725839Speter	else if (*ds != '\0')
70825839Speter	    nd->start = Make_Date (ds);
70925839Speter	else
71025839Speter	{
71125839Speter	  /* 1970 was the beginning of time, as far as get_date and
71225839Speter	     Make_Date are concerned.  FIXME: That is true only if time_t
71325839Speter	     is a POSIX-style time and there is nothing in ANSI that
71425839Speter	     mandates that.  It would be cleaner to set a flag saying
71525839Speter	     whether or not there is a start date.  */
71625839Speter	    nd->start = Make_Date ("1/1/1970 UTC");
71725839Speter	}
71825839Speter
71925839Speter	if (*de != '\0')
72025839Speter	    nd->end = Make_Date (de);
72125839Speter	else
72225839Speter	{
72325839Speter	    /* We want to set the end date to some time sufficiently far
72425839Speter	       in the future to pick up all revisions that have been
72525839Speter	       created since the specified date and the time `cvs log'
72625839Speter	       completes.  FIXME: The date in question only makes sense
72725839Speter	       if time_t is a POSIX-style time and it is 32 bits
72825839Speter	       and signed.  We should instead be setting a flag saying
72925839Speter	       whether or not there is an end date.  Note that using
73025839Speter	       something like "next week" would break the testsuite (and,
73125839Speter	       perhaps less importantly, loses if the clock is set grossly
73225839Speter	       wrong).  */
73325839Speter	    nd->end = Make_Date ("2038-01-01");
73425839Speter	}
73525839Speter
73625839Speter	nd->next = *pd;
73725839Speter	*pd = nd;
73825839Speter
73925839Speter	copy = cpend;
74025839Speter    }
74125839Speter
74225839Speter    free (orig_copy);
74325839Speter}
74425839Speter
74525839Speter/*
74625839Speter * Parse a comma separated list of items, and add each one to *PLIST.
74725839Speter */
74825839Speterstatic void
74925839Speterlog_parse_list (plist, argstring)
75025839Speter    List **plist;
75125839Speter    const char *argstring;
75225839Speter{
75325839Speter    while (1)
75425839Speter    {
75525839Speter	Node *p;
75625839Speter	char *cp;
75725839Speter
75825839Speter	p = getnode ();
75925839Speter
76025839Speter	cp = strchr (argstring, ',');
76125839Speter	if (cp == NULL)
76225839Speter	    p->key = xstrdup (argstring);
76325839Speter	else
76425839Speter	{
76525839Speter	    size_t len;
76625839Speter
76725839Speter	    len = cp - argstring;
76825839Speter	    p->key = xmalloc (len + 1);
76925839Speter	    strncpy (p->key, argstring, len);
770107484Speter	    p->key[len] = '\0';
77125839Speter	}
77225839Speter
77325839Speter	if (*plist == NULL)
77425839Speter	    *plist = getlist ();
77525839Speter	if (addnode (*plist, p) != 0)
77625839Speter	    freenode (p);
77725839Speter
77825839Speter	if (cp == NULL)
77925839Speter	    break;
78025839Speter
78125839Speter	argstring = cp + 1;
78225839Speter    }
78325839Speter}
78425839Speter
78532785Speterstatic int printlock_proc PROTO ((Node *, void *));
78632785Speter
78732785Speterstatic int
78832785Speterprintlock_proc (lock, foo)
78932785Speter    Node *lock;
79032785Speter    void *foo;
79132785Speter{
79232785Speter    cvs_output ("\n\t", 2);
79332785Speter    cvs_output (lock->data, 0);
79432785Speter    cvs_output (": ", 2);
79532785Speter    cvs_output (lock->key, 0);
79632785Speter    return 0;
79732785Speter}
79832785Speter
799128266Speter
800128266Speter
80125839Speter/*
80217721Speter * Do an rlog on a file
80317721Speter */
80417721Speterstatic int
80525839Speterlog_fileproc (callerdat, finfo)
80625839Speter    void *callerdat;
80717721Speter    struct file_info *finfo;
80817721Speter{
80925839Speter    struct log_data *log_data = (struct log_data *) callerdat;
81017721Speter    Node *p;
811102840Speter    int selrev = -1;
81217721Speter    RCSNode *rcsfile;
81325839Speter    char buf[50];
814128266Speter    struct revlist *revlist = NULL;
81525839Speter    struct log_data_and_rcs log_data_and_rcs;
81617721Speter
81717721Speter    if ((rcsfile = finfo->rcs) == NULL)
81817721Speter    {
81917721Speter	/* no rcs file.  What *do* we know about this file? */
82017721Speter	p = findnode (finfo->entries, finfo->file);
82117721Speter	if (p != NULL)
82217721Speter	{
823128266Speter	    Entnode *e = p->data;
824128266Speter
82532785Speter	    if (e->version[0] == '0' && e->version[1] == '\0')
82617721Speter	    {
82717721Speter		if (!really_quiet)
82817721Speter		    error (0, 0, "%s has been added, but not committed",
82917721Speter			   finfo->file);
830128266Speter		return 0;
83117721Speter	    }
83217721Speter	}
83317721Speter
83417721Speter	if (!really_quiet)
83517721Speter	    error (0, 0, "nothing known about %s", finfo->file);
83617721Speter
837128266Speter	return 1;
83817721Speter    }
83917721Speter
840102840Speter    if (log_data->sup_header || !log_data->nameonly)
841102840Speter    {
842102840Speter
843102840Speter	/* We will need all the information in the RCS file.  */
844102840Speter	RCS_fully_parse (rcsfile);
845102840Speter
846102840Speter	/* Turn any symbolic revisions in the revision list into numeric
847102840Speter	   revisions.  */
848102840Speter	revlist = log_expand_revlist (rcsfile, log_data->revlist,
849102840Speter				      log_data->default_branch);
850128266Speter	if (log_data->sup_header
851128266Speter            || (!log_data->header && !log_data->long_header))
852102840Speter	{
853102840Speter	    log_data_and_rcs.log_data = log_data;
854102840Speter	    log_data_and_rcs.revlist = revlist;
855102840Speter	    log_data_and_rcs.rcs = rcsfile;
856102840Speter
857102840Speter	    /* If any single dates were specified, we need to identify the
858102840Speter	       revisions they select.  Each one selects the single
859102840Speter	       revision, which is otherwise selected, of that date or
860102840Speter	       earlier.  The log_fix_singledate routine will fill in the
861102840Speter	       start date for each specific revision.  */
862102840Speter	    if (log_data->singledatelist != NULL)
863102840Speter		walklist (rcsfile->versions, log_fix_singledate,
864128266Speter			  (void *)&log_data_and_rcs);
865102840Speter
866102840Speter	    selrev = walklist (rcsfile->versions, log_count_print,
867128266Speter			       (void *)&log_data_and_rcs);
868128266Speter	    if (log_data->sup_header && selrev == 0)
869128266Speter	    {
870128266Speter		log_free_revlist (revlist);
871128266Speter		return 0;
872128266Speter	    }
873102840Speter	}
874102840Speter
875102840Speter    }
876102840Speter
87725839Speter    if (log_data->nameonly)
87817721Speter    {
87925839Speter	cvs_output (rcsfile->path, 0);
88025839Speter	cvs_output ("\n", 1);
881128266Speter	log_free_revlist (revlist);
88225839Speter	return 0;
88317721Speter    }
88417721Speter
88525839Speter    /* The output here is intended to be exactly compatible with the
88625839Speter       output of rlog.  I'm not sure whether this code should be here
88725839Speter       or in rcs.c; I put it here because it is specific to the log
88825839Speter       function, even though it uses information gathered by the
88925839Speter       functions in rcs.c.  */
89025839Speter
89125839Speter    cvs_output ("\n", 1);
89225839Speter
89325839Speter    cvs_output ("RCS file: ", 0);
89425839Speter    cvs_output (rcsfile->path, 0);
89525839Speter
89681404Speter    if (!is_rlog)
89717721Speter    {
89881404Speter	cvs_output ("\nWorking file: ", 0);
89981404Speter	if (finfo->update_dir[0] != '\0')
90081404Speter	{
90181404Speter	    cvs_output (finfo->update_dir, 0);
90281404Speter	    cvs_output ("/", 0);
90381404Speter	}
90425839Speter	cvs_output (finfo->file, 0);
90517721Speter    }
90617721Speter
90725839Speter    cvs_output ("\nhead:", 0);
90825839Speter    if (rcsfile->head != NULL)
90917721Speter    {
91025839Speter	cvs_output (" ", 1);
91125839Speter	cvs_output (rcsfile->head, 0);
91217721Speter    }
91325839Speter
91425839Speter    cvs_output ("\nbranch:", 0);
91525839Speter    if (rcsfile->branch != NULL)
91625839Speter    {
91725839Speter	cvs_output (" ", 1);
91825839Speter	cvs_output (rcsfile->branch, 0);
91925839Speter    }
92025839Speter
92125839Speter    cvs_output ("\nlocks:", 0);
92232785Speter    if (rcsfile->strict_locks)
92332785Speter	cvs_output (" strict", 0);
92432785Speter    walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
92525839Speter
92625839Speter    cvs_output ("\naccess list:", 0);
92732785Speter    if (rcsfile->access != NULL)
92825839Speter    {
92932785Speter	const char *cp;
93032785Speter
93132785Speter	cp = rcsfile->access;
93232785Speter	while (*cp != '\0')
93325839Speter	{
93425839Speter		const char *cp2;
93525839Speter
93625839Speter		cvs_output ("\n\t", 2);
93725839Speter		cp2 = cp;
938128266Speter		while (!isspace ((unsigned char) *cp2) && *cp2 != '\0')
93925839Speter		    ++cp2;
94025839Speter		cvs_output (cp, cp2 - cp);
94125839Speter		cp = cp2;
94254427Speter		while (isspace ((unsigned char) *cp) && *cp != '\0')
94325839Speter		    ++cp;
94425839Speter	}
94525839Speter    }
94625839Speter
947128266Speter    if (!log_data->notags)
94825839Speter    {
94925839Speter	List *syms;
95025839Speter
95125839Speter	cvs_output ("\nsymbolic names:", 0);
95225839Speter	syms = RCS_symbols (rcsfile);
95325839Speter	walklist (syms, log_symbol, NULL);
95425839Speter    }
95525839Speter
95625839Speter    cvs_output ("\nkeyword substitution: ", 0);
95725839Speter    if (rcsfile->expand == NULL)
95825839Speter	cvs_output ("kv", 2);
95925839Speter    else
96025839Speter	cvs_output (rcsfile->expand, 0);
96125839Speter
96225839Speter    cvs_output ("\ntotal revisions: ", 0);
96325839Speter    sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
96425839Speter    cvs_output (buf, 0);
96525839Speter
966102840Speter    if (selrev >= 0)
96725839Speter    {
96825839Speter	cvs_output (";\tselected revisions: ", 0);
969102840Speter	sprintf (buf, "%d", selrev);
97025839Speter	cvs_output (buf, 0);
97125839Speter    }
97225839Speter
97325839Speter    cvs_output ("\n", 1);
97425839Speter
975128266Speter    if (!log_data->header || log_data->long_header)
97625839Speter    {
97725839Speter	cvs_output ("description:\n", 0);
97832785Speter	if (rcsfile->desc != NULL)
97932785Speter	    cvs_output (rcsfile->desc, 0);
98025839Speter    }
98125839Speter
982128266Speter    if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
98325839Speter    {
98425839Speter	p = findnode (rcsfile->versions, rcsfile->head);
98525839Speter	if (p == NULL)
98625839Speter	    error (1, 0, "can not find head revision in `%s'",
98725839Speter		   finfo->fullname);
98825839Speter	while (p != NULL)
98925839Speter	{
990128266Speter	    RCSVers *vers = p->data;
99125839Speter
99225839Speter	    log_version (log_data, revlist, rcsfile, vers, 1);
99325839Speter	    if (vers->next == NULL)
99425839Speter		p = NULL;
99525839Speter	    else
99625839Speter	    {
99725839Speter		p = findnode (rcsfile->versions, vers->next);
99825839Speter		if (p == NULL)
99925839Speter		    error (1, 0, "can not find next revision `%s' in `%s'",
100025839Speter			   vers->next, finfo->fullname);
100125839Speter	    }
100225839Speter	}
100325839Speter
100425839Speter	log_tree (log_data, revlist, rcsfile, rcsfile->head);
100525839Speter    }
100625839Speter
100725839Speter    cvs_output("\
100825839Speter=============================================================================\n",
100925839Speter	       0);
101025839Speter
101125839Speter    /* Free up the new revlist and restore the old one.  */
101225839Speter    log_free_revlist (revlist);
101325839Speter
101425839Speter    /* If singledatelist is not NULL, free up the start dates we added
101525839Speter       to it.  */
101625839Speter    if (log_data->singledatelist != NULL)
101725839Speter    {
101825839Speter	struct datelist *d;
101925839Speter
102025839Speter	for (d = log_data->singledatelist; d != NULL; d = d->next)
102125839Speter	{
102225839Speter	    if (d->start != NULL)
102325839Speter		free (d->start);
102425839Speter	    d->start = NULL;
102525839Speter	}
102625839Speter    }
102725839Speter
102825839Speter    return 0;
102917721Speter}
103017721Speter
1031128266Speter
1032128266Speter
103317721Speter/*
103425839Speter * Fix up a revision list in order to compare it against versions.
103525839Speter * Expand any symbolic revisions.
103625839Speter */
103725839Speterstatic struct revlist *
103825839Speterlog_expand_revlist (rcs, revlist, default_branch)
103925839Speter    RCSNode *rcs;
104025839Speter    struct option_revlist *revlist;
104125839Speter    int default_branch;
104225839Speter{
104325839Speter    struct option_revlist *r;
104425839Speter    struct revlist *ret, **pr;
104525839Speter
104625839Speter    ret = NULL;
104725839Speter    pr = &ret;
104825839Speter    for (r = revlist; r != NULL; r = r->next)
104925839Speter    {
105025839Speter	struct revlist *nr;
105125839Speter
105225839Speter	nr = (struct revlist *) xmalloc (sizeof *nr);
105381404Speter	nr->inclusive = r->inclusive;
105425839Speter
105525839Speter	if (r->first == NULL && r->last == NULL)
105625839Speter	{
105725839Speter	    /* If both first and last are NULL, it means that we want
105825839Speter	       just the head of the default branch, which is RCS_head.  */
105925839Speter	    nr->first = RCS_head (rcs);
106025839Speter	    nr->last = xstrdup (nr->first);
106125839Speter	    nr->fields = numdots (nr->first) + 1;
106225839Speter	}
106325839Speter	else if (r->branchhead)
106425839Speter	{
106525839Speter	    char *branch;
106625839Speter
106725839Speter	    /* Print just the head of the branch.  */
106854427Speter	    if (isdigit ((unsigned char) r->first[0]))
106925839Speter		nr->first = RCS_getbranch (rcs, r->first, 1);
107025839Speter	    else
107125839Speter	    {
107225839Speter		branch = RCS_whatbranch (rcs, r->first);
107325839Speter		if (branch == NULL)
1074102840Speter		    nr->first = NULL;
1075102840Speter		else
107625839Speter		{
1077102840Speter		    nr->first = RCS_getbranch (rcs, branch, 1);
1078102840Speter		    free (branch);
107925839Speter		}
108025839Speter	    }
108125839Speter	    if (nr->first == NULL)
108225839Speter	    {
1083102840Speter		error (0, 0, "warning: no branch `%s' in `%s'",
108425839Speter		       r->first, rcs->path);
1085102840Speter		nr->last = NULL;
1086102840Speter		nr->fields = 0;
108725839Speter	    }
1088102840Speter	    else
1089102840Speter	    {
1090102840Speter		nr->last = xstrdup (nr->first);
1091102840Speter		nr->fields = numdots (nr->first) + 1;
1092102840Speter	    }
109325839Speter	}
109425839Speter	else
109525839Speter	{
109654427Speter	    if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
109725839Speter		nr->first = xstrdup (r->first);
109825839Speter	    else
109925839Speter	    {
110025839Speter		if (RCS_nodeisbranch (rcs, r->first))
110125839Speter		    nr->first = RCS_whatbranch (rcs, r->first);
110225839Speter		else
110325839Speter		    nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
110425839Speter		if (nr->first == NULL)
110525839Speter		{
110625839Speter		    error (0, 0, "warning: no revision `%s' in `%s'",
110725839Speter			   r->first, rcs->path);
110825839Speter		}
110925839Speter	    }
111025839Speter
1111102840Speter	    if (r->last == r->first || (r->last != NULL && r->first != NULL &&
1112102840Speter					strcmp (r->last, r->first) == 0))
111325839Speter		nr->last = xstrdup (nr->first);
111454427Speter	    else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
111525839Speter		nr->last = xstrdup (r->last);
111625839Speter	    else
111725839Speter	    {
111825839Speter		if (RCS_nodeisbranch (rcs, r->last))
111925839Speter		    nr->last = RCS_whatbranch (rcs, r->last);
112025839Speter		else
112125839Speter		    nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
112225839Speter		if (nr->last == NULL)
112325839Speter		{
112425839Speter		    error (0, 0, "warning: no revision `%s' in `%s'",
112525839Speter			   r->last, rcs->path);
112625839Speter		}
112725839Speter	    }
112825839Speter
112925839Speter	    /* Process the revision numbers the same way that rlog
113025839Speter               does.  This code is a bit cryptic for my tastes, but
113125839Speter               keeping the same implementation as rlog ensures a
113225839Speter               certain degree of compatibility.  */
1133128266Speter	    if (r->first == NULL && nr->last != NULL)
113425839Speter	    {
1135128266Speter		nr->fields = numdots (nr->last) + 1;
1136128266Speter		if (nr->fields < 2)
1137128266Speter		    nr->first = xstrdup (".0");
113825839Speter		else
113925839Speter		{
1140128266Speter		    char *cp;
114125839Speter
1142128266Speter		    nr->first = xstrdup (nr->last);
1143128266Speter		    cp = strrchr (nr->first, '.');
1144128266Speter		    strcpy (cp + 1, "0");
114525839Speter		}
114625839Speter	    }
1147128266Speter	    else if (r->last == NULL && nr->first != NULL)
114825839Speter	    {
114925839Speter		nr->fields = numdots (nr->first) + 1;
115025839Speter		nr->last = xstrdup (nr->first);
115125839Speter		if (nr->fields < 2)
115225839Speter		    nr->last[0] = '\0';
115325839Speter		else
115425839Speter		{
115525839Speter		    char *cp;
115625839Speter
115725839Speter		    cp = strrchr (nr->last, '.');
115825839Speter		    *cp = '\0';
115925839Speter		}
116025839Speter	    }
1161107484Speter	    else if (nr->first == NULL || nr->last == NULL)
1162107484Speter		nr->fields = 0;
1163107484Speter	    else if (strcmp (nr->first, nr->last) == 0)
1164107484Speter		nr->fields = numdots (nr->last) + 1;
1165107484Speter	    else
116625839Speter	    {
1167107484Speter		int ord;
1168107484Speter		int dots1 = numdots (nr->first);
1169107484Speter		int dots2 = numdots (nr->last);
1170107484Speter		if (dots1 > dots2 || (dots1 == dots2 &&
1171107484Speter		    version_compare (nr->first, nr->last, dots1 + 1) > 0))
117225839Speter		{
1173107484Speter		    char *tmp = nr->first;
1174107484Speter		    nr->first = nr->last;
1175107484Speter		    nr->last = tmp;
1176107484Speter		    nr->fields = dots2 + 1;
1177107484Speter		    dots2 = dots1;
1178107484Speter		    dots1 = nr->fields - 1;
1179107484Speter		}
1180107484Speter		else
1181107484Speter		    nr->fields = dots1 + 1;
1182107484Speter		dots1 += (nr->fields & 1);
1183107484Speter		ord = version_compare (nr->first, nr->last, dots1);
1184107484Speter		if (ord > 0 || (nr->fields > 2 && ord < 0))
1185107484Speter		{
118625839Speter		    error (0, 0,
118725839Speter			   "invalid branch or revision pair %s:%s in `%s'",
118825839Speter			   r->first, r->last, rcs->path);
118925839Speter		    free (nr->first);
1190102840Speter		    nr->first = NULL;
119125839Speter		    free (nr->last);
1192102840Speter		    nr->last = NULL;
1193102840Speter		    nr->fields = 0;
119425839Speter		}
1195107484Speter		else
119625839Speter		{
1197107484Speter		    if (nr->fields <= dots2 && (nr->fields & 1))
1198107484Speter		    {
1199107484Speter			char *p = xmalloc (strlen (nr->first) + 3);
1200107484Speter			strcpy (p, nr->first);
1201107484Speter			strcat (p, ".0");
1202107484Speter			free (nr->first);
1203107484Speter			nr->first = p;
1204107484Speter			++nr->fields;
1205107484Speter		    }
1206107484Speter		    while (nr->fields <= dots2)
1207107484Speter		    {
1208107484Speter			char *p;
1209107484Speter			int i;
121025839Speter
1211107484Speter			nr->next = NULL;
1212107484Speter			*pr = nr;
1213107484Speter			nr = (struct revlist *) xmalloc (sizeof *nr);
1214107484Speter			nr->inclusive = 1;
1215107484Speter			nr->first = xstrdup ((*pr)->last);
1216107484Speter			nr->last = xstrdup ((*pr)->last);
1217107484Speter			nr->fields = (*pr)->fields;
1218107484Speter			p = (*pr)->last;
1219107484Speter			for (i = 0; i < nr->fields; i++)
1220107484Speter			    p = strchr (p, '.') + 1;
1221107484Speter			p[-1] = '\0';
1222107484Speter			p = strchr (nr->first + (p - (*pr)->last), '.');
1223107484Speter			if (p != NULL)
1224107484Speter			{
1225107484Speter			    *++p = '0';
1226107484Speter			    *++p = '\0';
1227107484Speter			    nr->fields += 2;
1228107484Speter			}
1229107484Speter			else
1230107484Speter			    ++nr->fields;
1231107484Speter			pr = &(*pr)->next;
1232107484Speter		    }
123325839Speter		}
123425839Speter	    }
123525839Speter	}
123625839Speter
123725839Speter	nr->next = NULL;
123825839Speter	*pr = nr;
123925839Speter	pr = &nr->next;
124025839Speter    }
124125839Speter
124225839Speter    /* If the default branch was requested, add a revlist entry for
124325839Speter       it.  This is how rlog handles this option.  */
124425839Speter    if (default_branch
124525839Speter	&& (rcs->head != NULL || rcs->branch != NULL))
124625839Speter    {
124725839Speter	struct revlist *nr;
124825839Speter
124925839Speter	nr = (struct revlist *) xmalloc (sizeof *nr);
125025839Speter	if (rcs->branch != NULL)
125125839Speter	    nr->first = xstrdup (rcs->branch);
125225839Speter	else
125325839Speter	{
125425839Speter	    char *cp;
125525839Speter
125625839Speter	    nr->first = xstrdup (rcs->head);
125725839Speter	    cp = strrchr (nr->first, '.');
125825839Speter	    *cp = '\0';
125925839Speter	}
126025839Speter	nr->last = xstrdup (nr->first);
126125839Speter	nr->fields = numdots (nr->first) + 1;
126281404Speter	nr->inclusive = 1;
126325839Speter
126425839Speter	nr->next = NULL;
126525839Speter	*pr = nr;
126625839Speter    }
126725839Speter
126825839Speter    return ret;
126925839Speter}
127025839Speter
127125839Speter/*
127225839Speter * Free a revlist created by log_expand_revlist.
127325839Speter */
127425839Speterstatic void
127525839Speterlog_free_revlist (revlist)
127625839Speter    struct revlist *revlist;
127725839Speter{
127825839Speter    struct revlist *r;
127925839Speter
128025839Speter    r = revlist;
128125839Speter    while (r != NULL)
128225839Speter    {
128325839Speter	struct revlist *next;
128425839Speter
128525839Speter	if (r->first != NULL)
128625839Speter	    free (r->first);
128725839Speter	if (r->last != NULL)
128825839Speter	    free (r->last);
128925839Speter	next = r->next;
129025839Speter	free (r);
129125839Speter	r = next;
129225839Speter    }
129325839Speter}
129425839Speter
129525839Speter/*
129625839Speter * Return nonzero if a revision should be printed, based on the
129725839Speter * options provided.
129825839Speter */
129925839Speterstatic int
130025839Speterlog_version_requested (log_data, revlist, rcs, vnode)
130125839Speter    struct log_data *log_data;
130225839Speter    struct revlist *revlist;
130325839Speter    RCSNode *rcs;
130425839Speter    RCSVers *vnode;
130525839Speter{
130625839Speter    /* Handle the list of states from the -s option.  */
130725839Speter    if (log_data->statelist != NULL
130825839Speter	&& findnode (log_data->statelist, vnode->state) == NULL)
130925839Speter    {
131025839Speter	return 0;
131125839Speter    }
131225839Speter
131325839Speter    /* Handle the list of authors from the -w option.  */
131425839Speter    if (log_data->authorlist != NULL)
131525839Speter    {
131625839Speter	if (vnode->author != NULL
131725839Speter	    && findnode (log_data->authorlist, vnode->author) == NULL)
131825839Speter	{
131925839Speter	    return 0;
132025839Speter	}
132125839Speter    }
132225839Speter
132325839Speter    /* rlog considers all the -d options together when it decides
132425839Speter       whether to print a revision, so we must be compatible.  */
132525839Speter    if (log_data->datelist != NULL || log_data->singledatelist != NULL)
132625839Speter    {
132725839Speter	struct datelist *d;
132825839Speter
132925839Speter	for (d = log_data->datelist; d != NULL; d = d->next)
133025839Speter	{
133125839Speter	    int cmp;
133225839Speter
133325839Speter	    cmp = RCS_datecmp (vnode->date, d->start);
133425839Speter	    if (cmp > 0 || (cmp == 0 && d->inclusive))
133525839Speter	    {
133625839Speter		cmp = RCS_datecmp (vnode->date, d->end);
133725839Speter		if (cmp < 0 || (cmp == 0 && d->inclusive))
133825839Speter		    break;
133925839Speter	    }
134025839Speter	}
134125839Speter
134225839Speter	if (d == NULL)
134325839Speter	{
134425839Speter	    /* Look through the list of specific dates.  We want to
134525839Speter	       select the revision with the exact date found in the
134625839Speter	       start field.  The commit code ensures that it is
134725839Speter	       impossible to check in multiple revisions of a single
134825839Speter	       file in a single second, so checking the date this way
134925839Speter	       should never select more than one revision.  */
135025839Speter	    for (d = log_data->singledatelist; d != NULL; d = d->next)
135125839Speter	    {
135225839Speter		if (d->start != NULL
135325839Speter		    && RCS_datecmp (vnode->date, d->start) == 0)
135425839Speter		{
135525839Speter		    break;
135625839Speter		}
135725839Speter	    }
135825839Speter
135925839Speter	    if (d == NULL)
136025839Speter		return 0;
136125839Speter	}
136225839Speter    }
136325839Speter
136425839Speter    /* If the -r or -b options were used, REVLIST will be non NULL,
136525839Speter       and we print the union of the specified revisions.  */
136625839Speter    if (revlist != NULL)
136725839Speter    {
136825839Speter	char *v;
136925839Speter	int vfields;
137025839Speter	struct revlist *r;
137125839Speter
137225839Speter	/* This code is taken from rlog.  */
137325839Speter	v = vnode->version;
137425839Speter	vfields = numdots (v) + 1;
137525839Speter	for (r = revlist; r != NULL; r = r->next)
137625839Speter	{
137781404Speter	    if (vfields == r->fields + (r->fields & 1) &&
1378102840Speter		(r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
1379102840Speter				version_compare (v, r->first, r->fields) > 0)
1380102840Speter		    && version_compare (v, r->last, r->fields) <= 0)
138125839Speter	    {
138225839Speter		return 1;
138325839Speter	    }
138425839Speter	}
138525839Speter
138625839Speter	/* If we get here, then the -b and/or the -r option was used,
138725839Speter           but did not match this revision, so we reject it.  */
138825839Speter
138925839Speter	return 0;
139025839Speter    }
139125839Speter
139225839Speter    /* By default, we print all revisions.  */
139325839Speter    return 1;
139425839Speter}
139525839Speter
1396128266Speter
1397128266Speter
139825839Speter/*
139925839Speter * Output a single symbol.  This is called via walklist.
140025839Speter */
140125839Speter/*ARGSUSED*/
140225839Speterstatic int
140325839Speterlog_symbol (p, closure)
140425839Speter    Node *p;
140525839Speter    void *closure;
140625839Speter{
140725839Speter    cvs_output ("\n\t", 2);
140825839Speter    cvs_output (p->key, 0);
140925839Speter    cvs_output (": ", 2);
141025839Speter    cvs_output (p->data, 0);
141125839Speter    return 0;
141225839Speter}
141325839Speter
1414128266Speter
1415128266Speter
141625839Speter/*
141725839Speter * Count the number of entries on a list.  This is called via walklist.
141825839Speter */
141925839Speter/*ARGSUSED*/
142025839Speterstatic int
142125839Speterlog_count (p, closure)
142225839Speter    Node *p;
142325839Speter    void *closure;
142425839Speter{
142525839Speter    return 1;
142625839Speter}
142725839Speter
1428128266Speter
1429128266Speter
143025839Speter/*
143125839Speter * Sort out a single date specification by narrowing down the date
143225839Speter * until we find the specific selected revision.
143325839Speter */
143425839Speterstatic int
143525839Speterlog_fix_singledate (p, closure)
143625839Speter    Node *p;
143725839Speter    void *closure;
143825839Speter{
143925839Speter    struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
144025839Speter    Node *pv;
144125839Speter    RCSVers *vnode;
144225839Speter    struct datelist *holdsingle, *holddate;
144325839Speter    int requested;
144425839Speter
144525839Speter    pv = findnode (data->rcs->versions, p->key);
144625839Speter    if (pv == NULL)
144725839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
144825839Speter	       p->key, data->rcs->path);
1449128266Speter    vnode = pv->data;
145025839Speter
145125839Speter    /* We are only interested if this revision passes any other tests.
145225839Speter       Temporarily clear log_data->singledatelist to avoid confusing
145325839Speter       log_version_requested.  We also clear log_data->datelist,
145425839Speter       because rlog considers all the -d options together.  We don't
145525839Speter       want to reject a revision because it does not match a date pair
145625839Speter       if we are going to select it on the basis of the singledate.  */
145725839Speter    holdsingle = data->log_data->singledatelist;
145825839Speter    data->log_data->singledatelist = NULL;
145925839Speter    holddate = data->log_data->datelist;
146025839Speter    data->log_data->datelist = NULL;
146125839Speter    requested = log_version_requested (data->log_data, data->revlist,
146225839Speter				       data->rcs, vnode);
146325839Speter    data->log_data->singledatelist = holdsingle;
146425839Speter    data->log_data->datelist = holddate;
146525839Speter
146625839Speter    if (requested)
146725839Speter    {
146825839Speter	struct datelist *d;
146925839Speter
147025839Speter	/* For each single date, if this revision is before the
147125839Speter	   specified date, but is closer than the previously selected
147225839Speter	   revision, select it instead.  */
147325839Speter	for (d = data->log_data->singledatelist; d != NULL; d = d->next)
147425839Speter	{
147525839Speter	    if (RCS_datecmp (vnode->date, d->end) <= 0
147625839Speter		&& (d->start == NULL
147725839Speter		    || RCS_datecmp (vnode->date, d->start) > 0))
147825839Speter	    {
147925839Speter		if (d->start != NULL)
148025839Speter		    free (d->start);
148125839Speter		d->start = xstrdup (vnode->date);
148225839Speter	    }
148325839Speter	}
148425839Speter    }
148525839Speter
148625839Speter    return 0;
148725839Speter}
148825839Speter
1489128266Speter
1490128266Speter
149125839Speter/*
149225839Speter * Count the number of revisions we are going to print.
149325839Speter */
149425839Speterstatic int
149525839Speterlog_count_print (p, closure)
149625839Speter    Node *p;
149725839Speter    void *closure;
149825839Speter{
149925839Speter    struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
150025839Speter    Node *pv;
150125839Speter
150225839Speter    pv = findnode (data->rcs->versions, p->key);
150325839Speter    if (pv == NULL)
150425839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
150525839Speter	       p->key, data->rcs->path);
150625839Speter    if (log_version_requested (data->log_data, data->revlist, data->rcs,
1507128266Speter			       pv->data))
150825839Speter	return 1;
150925839Speter    else
151025839Speter	return 0;
151125839Speter}
151225839Speter
151325839Speter/*
151425839Speter * Print the list of changes, not including the trunk, in reverse
151525839Speter * order for each branch.
151625839Speter */
151725839Speterstatic void
151825839Speterlog_tree (log_data, revlist, rcs, ver)
151925839Speter    struct log_data *log_data;
152025839Speter    struct revlist *revlist;
152125839Speter    RCSNode *rcs;
152225839Speter    const char *ver;
152325839Speter{
152425839Speter    Node *p;
152525839Speter    RCSVers *vnode;
152625839Speter
152725839Speter    p = findnode (rcs->versions, ver);
152825839Speter    if (p == NULL)
152925839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
153025839Speter	       ver, rcs->path);
1531128266Speter    vnode = p->data;
153225839Speter    if (vnode->next != NULL)
153325839Speter	log_tree (log_data, revlist, rcs, vnode->next);
153425839Speter    if (vnode->branches != NULL)
153525839Speter    {
153625839Speter	Node *head, *branch;
153725839Speter
153825839Speter	/* We need to do the branches in reverse order.  This breaks
153925839Speter           the List abstraction, but so does most of the branch
154025839Speter           manipulation in rcs.c.  */
154125839Speter	head = vnode->branches->list;
154225839Speter	for (branch = head->prev; branch != head; branch = branch->prev)
154325839Speter	{
154425839Speter	    log_abranch (log_data, revlist, rcs, branch->key);
154525839Speter	    log_tree (log_data, revlist, rcs, branch->key);
154625839Speter	}
154725839Speter    }
154825839Speter}
154925839Speter
155025839Speter/*
155125839Speter * Log the changes for a branch, in reverse order.
155225839Speter */
155325839Speterstatic void
155425839Speterlog_abranch (log_data, revlist, rcs, ver)
155525839Speter    struct log_data *log_data;
155625839Speter    struct revlist *revlist;
155725839Speter    RCSNode *rcs;
155825839Speter    const char *ver;
155925839Speter{
156025839Speter    Node *p;
156125839Speter    RCSVers *vnode;
156225839Speter
156325839Speter    p = findnode (rcs->versions, ver);
156425839Speter    if (p == NULL)
156525839Speter	error (1, 0, "missing version `%s' in RCS file `%s'",
156625839Speter	       ver, rcs->path);
1567128266Speter    vnode = p->data;
156825839Speter    if (vnode->next != NULL)
156925839Speter	log_abranch (log_data, revlist, rcs, vnode->next);
157025839Speter    log_version (log_data, revlist, rcs, vnode, 0);
157125839Speter}
157225839Speter
157325839Speter/*
157425839Speter * Print the log output for a single version.
157525839Speter */
157625839Speterstatic void
157725839Speterlog_version (log_data, revlist, rcs, ver, trunk)
157825839Speter    struct log_data *log_data;
157925839Speter    struct revlist *revlist;
158025839Speter    RCSNode *rcs;
158125839Speter    RCSVers *ver;
158225839Speter    int trunk;
158325839Speter{
158425839Speter    Node *p;
158525839Speter    int year, mon, mday, hour, min, sec;
158625839Speter    char buf[100];
158725839Speter    Node *padd, *pdel;
158825839Speter
158925839Speter    if (! log_version_requested (log_data, revlist, rcs, ver))
159025839Speter	return;
159125839Speter
159225839Speter    cvs_output ("----------------------------\nrevision ", 0);
159325839Speter    cvs_output (ver->version, 0);
159425839Speter
159532785Speter    p = findnode (RCS_getlocks (rcs), ver->version);
159625839Speter    if (p != NULL)
159725839Speter    {
159825839Speter	cvs_output ("\tlocked by: ", 0);
159925839Speter	cvs_output (p->data, 0);
160025839Speter	cvs_output (";", 1);
160125839Speter    }
160225839Speter
160325839Speter    cvs_output ("\ndate: ", 0);
160425839Speter    (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
160525839Speter		   &sec);
160625839Speter    if (year < 1900)
160725839Speter	year += 1900;
160825839Speter    sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
160925839Speter	     hour, min, sec);
161025839Speter    cvs_output (buf, 0);
161125839Speter
161225839Speter    cvs_output (";  author: ", 0);
161325839Speter    cvs_output (ver->author, 0);
161425839Speter
161525839Speter    cvs_output (";  state: ", 0);
161625839Speter    cvs_output (ver->state, 0);
161725839Speter    cvs_output (";", 1);
161825839Speter
161925839Speter    if (! trunk)
162025839Speter    {
162125839Speter	padd = findnode (ver->other, ";add");
162225839Speter	pdel = findnode (ver->other, ";delete");
162325839Speter    }
162425839Speter    else if (ver->next == NULL)
162525839Speter    {
162625839Speter	padd = NULL;
162725839Speter	pdel = NULL;
162825839Speter    }
162925839Speter    else
163025839Speter    {
163125839Speter	Node *nextp;
163225839Speter	RCSVers *nextver;
163325839Speter
163425839Speter	nextp = findnode (rcs->versions, ver->next);
163525839Speter	if (nextp == NULL)
163625839Speter	    error (1, 0, "missing version `%s' in `%s'", ver->next,
163725839Speter		   rcs->path);
1638128266Speter	nextver = nextp->data;
163925839Speter	pdel = findnode (nextver->other, ";add");
164025839Speter	padd = findnode (nextver->other, ";delete");
164125839Speter    }
164225839Speter
164325839Speter    if (padd != NULL)
164425839Speter    {
164525839Speter	cvs_output ("  lines: +", 0);
164625839Speter	cvs_output (padd->data, 0);
164725839Speter	cvs_output (" -", 2);
164825839Speter	cvs_output (pdel->data, 0);
164925839Speter    }
165025839Speter
165125839Speter    if (ver->branches != NULL)
165225839Speter    {
165325839Speter	cvs_output ("\nbranches:", 0);
165425839Speter	walklist (ver->branches, log_branch, (void *) NULL);
165525839Speter    }
165625839Speter
165725839Speter    cvs_output ("\n", 1);
165825839Speter
165925839Speter    p = findnode (ver->other, "log");
166032785Speter    /* The p->date == NULL case is the normal one for an empty log
166132785Speter       message (rcs-14 in sanity.sh).  I don't think the case where
166232785Speter       p->data is "" can happen (getrcskey in rcs.c checks for an
166332785Speter       empty string and set the value to NULL in that case).  My guess
166432785Speter       would be the p == NULL case would mean an RCS file which was
166532785Speter       missing the "log" keyword (which is illegal according to
166632785Speter       rcsfile.5).  */
1667128266Speter    if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
166825839Speter	cvs_output ("*** empty log message ***\n", 0);
166925839Speter    else
167025839Speter    {
167125839Speter	/* FIXME: Technically, the log message could contain a null
167225839Speter           byte.  */
167325839Speter	cvs_output (p->data, 0);
1674128266Speter	if (((char *)p->data)[strlen (p->data) - 1] != '\n')
167525839Speter	    cvs_output ("\n", 1);
167625839Speter    }
167725839Speter}
167825839Speter
167925839Speter/*
168025839Speter * Output a branch version.  This is called via walklist.
168125839Speter */
168225839Speter/*ARGSUSED*/
168325839Speterstatic int
168425839Speterlog_branch (p, closure)
168525839Speter    Node *p;
168625839Speter    void *closure;
168725839Speter{
168825839Speter    cvs_output ("  ", 2);
168925839Speter    if ((numdots (p->key) & 1) == 0)
169025839Speter	cvs_output (p->key, 0);
169125839Speter    else
169225839Speter    {
169325839Speter	char *f, *cp;
169425839Speter
169525839Speter	f = xstrdup (p->key);
169625839Speter	cp = strrchr (f, '.');
169725839Speter	*cp = '\0';
169825839Speter	cvs_output (f, 0);
169925839Speter	free (f);
170025839Speter    }
170125839Speter    cvs_output (";", 1);
170225839Speter    return 0;
170325839Speter}
170425839Speter
170525839Speter/*
170617721Speter * Print a warm fuzzy message
170717721Speter */
170817721Speter/* ARGSUSED */
170917721Speterstatic Dtype
171025839Speterlog_dirproc (callerdat, dir, repository, update_dir, entries)
171125839Speter    void *callerdat;
1712128266Speter    const char *dir;
1713128266Speter    const char *repository;
1714128266Speter    const char *update_dir;
171525839Speter    List *entries;
171617721Speter{
171717721Speter    if (!isdir (dir))
171817721Speter	return (R_SKIP_ALL);
171917721Speter
172017721Speter    if (!quiet)
172117721Speter	error (0, 0, "Logging %s", update_dir);
172217721Speter    return (R_PROCESS);
172317721Speter}
172425839Speter
172525839Speter/*
172625839Speter * Compare versions.  This is taken from RCS compartial.
172725839Speter */
172825839Speterstatic int
172925839Speterversion_compare (v1, v2, len)
173025839Speter    const char *v1;
173125839Speter    const char *v2;
173225839Speter    int len;
173325839Speter{
173425839Speter    while (1)
173525839Speter    {
173625839Speter	int d1, d2, r;
173725839Speter
173825839Speter	if (*v1 == '\0')
173925839Speter	    return 1;
174025839Speter	if (*v2 == '\0')
174125839Speter	    return -1;
174225839Speter
174325839Speter	while (*v1 == '0')
174425839Speter	    ++v1;
174554427Speter	for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
174625839Speter	    ;
174725839Speter
174825839Speter	while (*v2 == '0')
174925839Speter	    ++v2;
175054427Speter	for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
175125839Speter	    ;
175225839Speter
175325839Speter	if (d1 != d2)
175425839Speter	    return d1 < d2 ? -1 : 1;
175525839Speter
175625839Speter	r = memcmp (v1, v2, d1);
175725839Speter	if (r != 0)
175825839Speter	    return r;
175925839Speter
176025839Speter	--len;
176125839Speter	if (len == 0)
176225839Speter	    return 0;
176325839Speter
176425839Speter	v1 += d1;
176525839Speter	v2 += d1;
176625839Speter
176725839Speter	if (*v1 == '.')
176825839Speter	    ++v1;
176925839Speter	if (*v2 == '.')
177025839Speter	    ++v2;
177125839Speter    }
177225839Speter}
1773