xref: /openbsd/gnu/usr.bin/cvs/src/log.c (revision 5f354d28)
11e72d8d2Sderaadt /*
21e72d8d2Sderaadt  * Copyright (c) 1992, Brian Berliner and Jeff Polk
31e72d8d2Sderaadt  * Copyright (c) 1989-1992, Brian Berliner
41e72d8d2Sderaadt  *
51e72d8d2Sderaadt  * You may distribute under the terms of the GNU General Public License as
62286d8edStholo  * specified in the README file that comes with the CVS source distribution.
71e72d8d2Sderaadt  *
81e72d8d2Sderaadt  * Print Log Information
91e72d8d2Sderaadt  *
101e72d8d2Sderaadt  * Prints the RCS "log" (rlog) information for the specified files.  With no
111e72d8d2Sderaadt  * argument, prints the log information for all the files in the directory
121e72d8d2Sderaadt  * (recursive by default).
131e72d8d2Sderaadt  */
141e72d8d2Sderaadt 
151e72d8d2Sderaadt #include "cvs.h"
161e72d8d2Sderaadt 
1750bf276cStholo /* This structure holds information parsed from the -r option.  */
1850bf276cStholo 
1950bf276cStholo struct option_revlist
2050bf276cStholo {
2150bf276cStholo     /* The next -r option.  */
2250bf276cStholo     struct option_revlist *next;
2350bf276cStholo     /* The first revision to print.  This is NULL if the range is
2450bf276cStholo        :rev, or if no revision is given.  */
2550bf276cStholo     char *first;
2650bf276cStholo     /* The last revision to print.  This is NULL if the range is rev:,
2750bf276cStholo        or if no revision is given.  If there is no colon, first and
2850bf276cStholo        last are the same.  */
2950bf276cStholo     char *last;
3050bf276cStholo     /* Nonzero if there was a trailing `.', which means to print only
3150bf276cStholo        the head revision of a branch.  */
3250bf276cStholo     int branchhead;
3343c1707eStholo     /* Nonzero if first and last are inclusive.  */
3443c1707eStholo     int inclusive;
3550bf276cStholo };
3650bf276cStholo 
3750bf276cStholo /* This structure holds information derived from option_revlist given
3850bf276cStholo    a particular RCS file.  */
3950bf276cStholo 
4050bf276cStholo struct revlist
4150bf276cStholo {
4250bf276cStholo     /* The next pair.  */
4350bf276cStholo     struct revlist *next;
4450bf276cStholo     /* The first numeric revision to print.  */
4550bf276cStholo     char *first;
4650bf276cStholo     /* The last numeric revision to print.  */
4750bf276cStholo     char *last;
4850bf276cStholo     /* The number of fields in these revisions (one more than
4950bf276cStholo        numdots).  */
5050bf276cStholo     int fields;
5143c1707eStholo     /* Whether first & last are to be included or excluded.  */
5243c1707eStholo     int inclusive;
5350bf276cStholo };
5450bf276cStholo 
5550bf276cStholo /* This structure holds information parsed from the -d option.  */
5650bf276cStholo 
5750bf276cStholo struct datelist
5850bf276cStholo {
5950bf276cStholo     /* The next date.  */
6050bf276cStholo     struct datelist *next;
6150bf276cStholo     /* The starting date.  */
6250bf276cStholo     char *start;
6350bf276cStholo     /* The ending date.  */
6450bf276cStholo     char *end;
6550bf276cStholo     /* Nonzero if the range is inclusive rather than exclusive.  */
6650bf276cStholo     int inclusive;
6750bf276cStholo };
6850bf276cStholo 
6950bf276cStholo /* This structure is used to pass information through start_recursion.  */
7050bf276cStholo struct log_data
7150bf276cStholo {
7250bf276cStholo     /* Nonzero if the -R option was given, meaning that only the name
7350bf276cStholo        of the RCS file should be printed.  */
7450bf276cStholo     int nameonly;
7550bf276cStholo     /* Nonzero if the -h option was given, meaning that only header
7650bf276cStholo        information should be printed.  */
7750bf276cStholo     int header;
7850bf276cStholo     /* Nonzero if the -t option was given, meaning that only the
7950bf276cStholo        header and the descriptive text should be printed.  */
8050bf276cStholo     int long_header;
8150bf276cStholo     /* Nonzero if the -N option was seen, meaning that tag information
8250bf276cStholo        should not be printed.  */
8350bf276cStholo     int notags;
8450bf276cStholo     /* Nonzero if the -b option was seen, meaning that only revisions
8550bf276cStholo        on the default branch should be printed.  */
8650bf276cStholo     int default_branch;
8750bf276cStholo     /* If not NULL, the value given for the -r option, which lists
8850bf276cStholo        sets of revisions to be printed.  */
8950bf276cStholo     struct option_revlist *revlist;
9050bf276cStholo     /* If not NULL, the date pairs given for the -d option, which
9150bf276cStholo        select date ranges to print.  */
9250bf276cStholo     struct datelist *datelist;
9350bf276cStholo     /* If not NULL, the single dates given for the -d option, which
9450bf276cStholo        select specific revisions to print based on a date.  */
9550bf276cStholo     struct datelist *singledatelist;
9650bf276cStholo     /* If not NULL, the list of states given for the -s option, which
9750bf276cStholo        only prints revisions of given states.  */
9850bf276cStholo     List *statelist;
9950bf276cStholo     /* If not NULL, the list of login names given for the -w option,
10050bf276cStholo        which only prints revisions checked in by given users.  */
10150bf276cStholo     List *authorlist;
10250bf276cStholo };
10350bf276cStholo 
10450bf276cStholo /* This structure is used to pass information through walklist.  */
10550bf276cStholo struct log_data_and_rcs
10650bf276cStholo {
10750bf276cStholo     struct log_data *log_data;
10850bf276cStholo     struct revlist *revlist;
10950bf276cStholo     RCSNode *rcs;
11050bf276cStholo };
11150bf276cStholo 
11243c1707eStholo static int rlog_proc PROTO((int argc, char **argv, char *xwhere,
11343c1707eStholo 			    char *mwhere, char *mfile, int shorten,
11443c1707eStholo 			    int local_specified, char *mname, char *msg));
11550bf276cStholo static Dtype log_dirproc PROTO ((void *callerdat, char *dir,
11650bf276cStholo 				 char *repository, char *update_dir,
11750bf276cStholo 				 List *entries));
11850bf276cStholo static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
11950bf276cStholo static struct option_revlist *log_parse_revlist PROTO ((const char *));
12050bf276cStholo static void log_parse_date PROTO ((struct log_data *, const char *));
12150bf276cStholo static void log_parse_list PROTO ((List **, const char *));
12250bf276cStholo static struct revlist *log_expand_revlist PROTO ((RCSNode *,
12350bf276cStholo 						  struct option_revlist *,
12450bf276cStholo 						  int));
12550bf276cStholo static void log_free_revlist PROTO ((struct revlist *));
12650bf276cStholo static int log_version_requested PROTO ((struct log_data *, struct revlist *,
12750bf276cStholo 					 RCSNode *, RCSVers *));
12850bf276cStholo static int log_symbol PROTO ((Node *, void *));
12950bf276cStholo static int log_count PROTO ((Node *, void *));
13050bf276cStholo static int log_fix_singledate PROTO ((Node *, void *));
13150bf276cStholo static int log_count_print PROTO ((Node *, void *));
13250bf276cStholo static void log_tree PROTO ((struct log_data *, struct revlist *,
13350bf276cStholo 			     RCSNode *, const char *));
13450bf276cStholo static void log_abranch PROTO ((struct log_data *, struct revlist *,
13550bf276cStholo 				RCSNode *, const char *));
13650bf276cStholo static void log_version PROTO ((struct log_data *, struct revlist *,
13750bf276cStholo 				RCSNode *, RCSVers *, int));
13850bf276cStholo static int log_branch PROTO ((Node *, void *));
13950bf276cStholo static int version_compare PROTO ((const char *, const char *, int));
1401e72d8d2Sderaadt 
14143c1707eStholo static struct log_data log_data;
14243c1707eStholo static int is_rlog;
14343c1707eStholo 
1441e72d8d2Sderaadt static const char *const log_usage[] =
1451e72d8d2Sderaadt {
14650bf276cStholo     "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
14750bf276cStholo     "    [-w[logins]] [files...]\n",
1481e72d8d2Sderaadt     "\t-l\tLocal directory only, no recursion.\n",
14950bf276cStholo     "\t-R\tOnly print name of RCS file.\n",
15050bf276cStholo     "\t-h\tOnly print header.\n",
15150bf276cStholo     "\t-t\tOnly print header and descriptive text.\n",
15250bf276cStholo     "\t-N\tDo not list tags.\n",
15350bf276cStholo     "\t-b\tOnly list revisions on the default branch.\n",
154f2badbebStobias     "\t-r[revisions]\tSpecify revision(s) to list.\n",
15543c1707eStholo     "\t   rev1:rev2   Between rev1 and rev2, including rev1 and rev2.\n",
15643c1707eStholo     "\t   rev1::rev2  Between rev1 and rev2, excluding rev1 and rev2.\n",
15743c1707eStholo     "\t   rev:        rev and following revisions on the same branch.\n",
15843c1707eStholo     "\t   rev::       After rev on the same branch.\n",
15943c1707eStholo     "\t   :rev        rev and previous revisions on the same branch.\n",
16043c1707eStholo     "\t   ::rev       Before rev on the same branch.\n",
16143c1707eStholo     "\t   rev         Just rev.\n",
16243c1707eStholo     "\t   branch      All revisions on the branch.\n",
16343c1707eStholo     "\t   branch.     The last revision on the branch.\n",
16450bf276cStholo     "\t-d dates\tSpecify dates (D1<D2 for range, D for latest before).\n",
16550bf276cStholo     "\t-s states\tOnly list revisions with specified states.\n",
16650bf276cStholo     "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
1672286d8edStholo     "(Specify the --help global option for a list of other help options)\n",
1681e72d8d2Sderaadt     NULL
1691e72d8d2Sderaadt };
1701e72d8d2Sderaadt 
171e77048c1Stholo #ifdef CLIENT_SUPPORT
172e77048c1Stholo 
173e77048c1Stholo /* Helper function for send_arg_list.  */
174e77048c1Stholo static int send_one PROTO ((Node *, void *));
175e77048c1Stholo 
176e77048c1Stholo static int
send_one(node,closure)177e77048c1Stholo send_one (node, closure)
178e77048c1Stholo     Node *node;
179e77048c1Stholo     void *closure;
180e77048c1Stholo {
181e77048c1Stholo     char *option = (char *) closure;
182e77048c1Stholo 
183e77048c1Stholo     send_to_server ("Argument ", 0);
184e77048c1Stholo     send_to_server (option, 0);
185e77048c1Stholo     if (strcmp (node->key, "@@MYSELF") == 0)
186e77048c1Stholo 	/* It is a bare -w option.  Note that we must send it as
187e77048c1Stholo 	   -w rather than messing with getcaller() or something (which on
188e77048c1Stholo 	   the client will return garbage).  */
189e77048c1Stholo 	;
190e77048c1Stholo     else
191e77048c1Stholo 	send_to_server (node->key, 0);
192e77048c1Stholo     send_to_server ("\012", 0);
193e77048c1Stholo     return 0;
194e77048c1Stholo }
195e77048c1Stholo 
196e77048c1Stholo /* For each element in ARG, send an argument consisting of OPTION
197e77048c1Stholo    concatenated with that element.  */
198e77048c1Stholo static void send_arg_list PROTO ((char *, List *));
199e77048c1Stholo 
200e77048c1Stholo static void
send_arg_list(option,arg)201e77048c1Stholo send_arg_list (option, arg)
202e77048c1Stholo     char *option;
203e77048c1Stholo     List *arg;
204e77048c1Stholo {
205e77048c1Stholo     if (arg == NULL)
206e77048c1Stholo 	return;
207e77048c1Stholo     walklist (arg, send_one, (void *)option);
208e77048c1Stholo }
209e77048c1Stholo 
210e77048c1Stholo #endif
211e77048c1Stholo 
2121e72d8d2Sderaadt int
cvslog(argc,argv)2131e72d8d2Sderaadt cvslog (argc, argv)
2141e72d8d2Sderaadt     int argc;
2151e72d8d2Sderaadt     char **argv;
2161e72d8d2Sderaadt {
21750bf276cStholo     int c;
2181e72d8d2Sderaadt     int err = 0;
2191e72d8d2Sderaadt     int local = 0;
220e77048c1Stholo     struct option_revlist **prl;
2211e72d8d2Sderaadt 
22243c1707eStholo     is_rlog = (strcmp (command_name, "rlog") == 0);
22343c1707eStholo 
2241e72d8d2Sderaadt     if (argc == -1)
2251e72d8d2Sderaadt 	usage (log_usage);
2261e72d8d2Sderaadt 
22750bf276cStholo     memset (&log_data, 0, sizeof log_data);
228e77048c1Stholo     prl = &log_data.revlist;
22950bf276cStholo 
2302770ece5Stholo     optind = 0;
231b6c02222Stholo     while ((c = getopt (argc, argv, "+bd:hlNRr::s:tw::")) != -1)
23250bf276cStholo     {
23350bf276cStholo 	switch (c)
23450bf276cStholo 	{
23550bf276cStholo 	    case 'b':
23650bf276cStholo 		log_data.default_branch = 1;
23750bf276cStholo 		break;
23850bf276cStholo 	    case 'd':
23950bf276cStholo 		log_parse_date (&log_data, optarg);
24050bf276cStholo 		break;
24150bf276cStholo 	    case 'h':
24250bf276cStholo 		log_data.header = 1;
24350bf276cStholo 		break;
24450bf276cStholo 	    case 'l':
2451e72d8d2Sderaadt 		local = 1;
24650bf276cStholo 		break;
24750bf276cStholo 	    case 'N':
24850bf276cStholo 		log_data.notags = 1;
24950bf276cStholo 		break;
25050bf276cStholo 	    case 'R':
25150bf276cStholo 		log_data.nameonly = 1;
25250bf276cStholo 		break;
25350bf276cStholo 	    case 'r':
254e77048c1Stholo 		*prl = log_parse_revlist (optarg);
255e77048c1Stholo 		prl = &(*prl)->next;
25650bf276cStholo 		break;
25750bf276cStholo 	    case 's':
25850bf276cStholo 		log_parse_list (&log_data.statelist, optarg);
25950bf276cStholo 		break;
26050bf276cStholo 	    case 't':
26150bf276cStholo 		log_data.long_header = 1;
26250bf276cStholo 		break;
26350bf276cStholo 	    case 'w':
26450bf276cStholo 		if (optarg != NULL)
26550bf276cStholo 		    log_parse_list (&log_data.authorlist, optarg);
26650bf276cStholo 		else
267e77048c1Stholo 		    log_parse_list (&log_data.authorlist, "@@MYSELF");
26850bf276cStholo 		break;
26950bf276cStholo 	    case '?':
27050bf276cStholo 	    default:
27150bf276cStholo 		usage (log_usage);
27250bf276cStholo 		break;
27350bf276cStholo 	}
27450bf276cStholo     }
27543c1707eStholo     argc -= optind;
27643c1707eStholo     argv += optind;
2771e72d8d2Sderaadt 
2781e72d8d2Sderaadt     wrap_setup ();
2791e72d8d2Sderaadt 
2801e72d8d2Sderaadt #ifdef CLIENT_SUPPORT
28143c1707eStholo     if (current_parsed_root->isremote)
28250bf276cStholo     {
283e77048c1Stholo 	struct datelist *p;
284e77048c1Stholo 	struct option_revlist *rp;
285e77048c1Stholo 	char datetmp[MAXDATELEN];
28650bf276cStholo 
2871e72d8d2Sderaadt 	/* We're the local client.  Fire up the remote server.  */
2881e72d8d2Sderaadt 	start_server ();
2891e72d8d2Sderaadt 
29043c1707eStholo 	if (is_rlog && !supported_request ("rlog"))
29143c1707eStholo 	    error (1, 0, "server does not support rlog");
29243c1707eStholo 
2931e72d8d2Sderaadt 	ign_setup ();
2941e72d8d2Sderaadt 
295e77048c1Stholo 	if (log_data.default_branch)
296e77048c1Stholo 	    send_arg ("-b");
2971e72d8d2Sderaadt 
298e77048c1Stholo 	while (log_data.datelist != NULL)
299e77048c1Stholo 	{
300e77048c1Stholo 	    p = log_data.datelist;
301e77048c1Stholo 	    log_data.datelist = p->next;
302e77048c1Stholo 	    send_to_server ("Argument -d\012", 0);
303e77048c1Stholo 	    send_to_server ("Argument ", 0);
304e77048c1Stholo 	    date_to_internet (datetmp, p->start);
305e77048c1Stholo 	    send_to_server (datetmp, 0);
306e77048c1Stholo 	    if (p->inclusive)
307e77048c1Stholo 		send_to_server ("<=", 0);
308e77048c1Stholo 	    else
309e77048c1Stholo 		send_to_server ("<", 0);
310e77048c1Stholo 	    date_to_internet (datetmp, p->end);
311e77048c1Stholo 	    send_to_server (datetmp, 0);
312e77048c1Stholo 	    send_to_server ("\012", 0);
313e77048c1Stholo 	    if (p->start)
314e77048c1Stholo 		free (p->start);
315e77048c1Stholo 	    if (p->end)
316e77048c1Stholo 		free (p->end);
317e77048c1Stholo 	    free (p);
318e77048c1Stholo 	}
319e77048c1Stholo 	while (log_data.singledatelist != NULL)
320e77048c1Stholo 	{
321e77048c1Stholo 	    p = log_data.singledatelist;
322e77048c1Stholo 	    log_data.singledatelist = p->next;
323e77048c1Stholo 	    send_to_server ("Argument -d\012", 0);
324e77048c1Stholo 	    send_to_server ("Argument ", 0);
325e77048c1Stholo 	    date_to_internet (datetmp, p->end);
326e77048c1Stholo 	    send_to_server (datetmp, 0);
327e77048c1Stholo 	    send_to_server ("\012", 0);
328e77048c1Stholo 	    if (p->end)
329e77048c1Stholo 		free (p->end);
330e77048c1Stholo 	    free (p);
331e77048c1Stholo 	}
332e77048c1Stholo 
333e77048c1Stholo 	if (log_data.header)
334e77048c1Stholo 	    send_arg ("-h");
335e77048c1Stholo 	if (local)
336e77048c1Stholo 	    send_arg("-l");
337e77048c1Stholo 	if (log_data.notags)
338e77048c1Stholo 	    send_arg("-N");
339e77048c1Stholo 	if (log_data.nameonly)
340e77048c1Stholo 	    send_arg("-R");
341e77048c1Stholo 	if (log_data.long_header)
342e77048c1Stholo 	    send_arg("-t");
343e77048c1Stholo 
344e77048c1Stholo 	while (log_data.revlist != NULL)
345e77048c1Stholo 	{
346e77048c1Stholo 	    rp = log_data.revlist;
347e77048c1Stholo 	    log_data.revlist = rp->next;
348e77048c1Stholo 	    send_to_server ("Argument -r", 0);
349e77048c1Stholo 	    if (rp->branchhead)
350e77048c1Stholo 	    {
351e77048c1Stholo 		if (rp->first != NULL)
352e77048c1Stholo 		    send_to_server (rp->first, 0);
353e77048c1Stholo 		send_to_server (".", 1);
354e77048c1Stholo 	    }
355e77048c1Stholo 	    else
356e77048c1Stholo 	    {
357e77048c1Stholo 		if (rp->first != NULL)
358e77048c1Stholo 		    send_to_server (rp->first, 0);
359e77048c1Stholo 		send_to_server (":", 1);
36043c1707eStholo 		if (!rp->inclusive)
36143c1707eStholo 		    send_to_server (":", 1);
362e77048c1Stholo 		if (rp->last != NULL)
363e77048c1Stholo 		    send_to_server (rp->last, 0);
364e77048c1Stholo 	    }
365e77048c1Stholo 	    send_to_server ("\012", 0);
366e77048c1Stholo 	    if (rp->first)
367e77048c1Stholo 		free (rp->first);
368e77048c1Stholo 	    if (rp->last)
369e77048c1Stholo 		free (rp->last);
370e77048c1Stholo 	    free (rp);
371e77048c1Stholo 	}
372e77048c1Stholo 	send_arg_list ("-s", log_data.statelist);
373e77048c1Stholo 	dellist (&log_data.statelist);
374e77048c1Stholo 	send_arg_list ("-w", log_data.authorlist);
375e77048c1Stholo 	dellist (&log_data.authorlist);
376e77048c1Stholo 
37743c1707eStholo 	if (is_rlog)
37843c1707eStholo 	{
37943c1707eStholo 	    int i;
38043c1707eStholo 	    for (i = 0; i < argc; i++)
38143c1707eStholo 		send_arg (argv[i]);
38243c1707eStholo 	    send_to_server ("rlog\012", 0);
38343c1707eStholo 	}
38443c1707eStholo 	else
38543c1707eStholo 	{
38643c1707eStholo 	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
38743c1707eStholo 	    send_file_names (argc, argv, SEND_EXPAND_WILD);
38813571821Stholo 	    send_to_server ("log\012", 0);
38943c1707eStholo 	}
3901e72d8d2Sderaadt         err = get_responses_and_close ();
3911e72d8d2Sderaadt 	return err;
3921e72d8d2Sderaadt     }
3931e72d8d2Sderaadt #endif
3941e72d8d2Sderaadt 
395e77048c1Stholo     /* OK, now that we know we are local/server, we can resolve @@MYSELF
396e77048c1Stholo        into our user name.  */
397e77048c1Stholo     if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
398e77048c1Stholo 	log_parse_list (&log_data.authorlist, getcaller ());
399e77048c1Stholo 
40043c1707eStholo     if (is_rlog)
40143c1707eStholo     {
40243c1707eStholo 	DBM *db;
40343c1707eStholo 	int i;
40443c1707eStholo 	db = open_module ();
40543c1707eStholo 	for (i = 0; i < argc; i++)
40643c1707eStholo 	{
40743c1707eStholo 	    err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
40843c1707eStholo 			     (char *) NULL, 0, 0, 0, 0, (char *) NULL);
40943c1707eStholo 	}
41043c1707eStholo 	close_module (db);
41143c1707eStholo     }
41243c1707eStholo     else
41343c1707eStholo     {
41443c1707eStholo 	err = rlog_proc (argc + 1, argv - 1, (char *) NULL,
41543c1707eStholo 			 (char *) NULL, (char *) NULL, 0, 0, (char *) NULL,
41643c1707eStholo 			 (char *) NULL);
41743c1707eStholo     }
418e77048c1Stholo 
419e77048c1Stholo     while (log_data.revlist)
420e77048c1Stholo     {
421e77048c1Stholo 	struct option_revlist *rl = log_data.revlist->next;
422e77048c1Stholo 	if (log_data.revlist->first)
423e77048c1Stholo 	    free (log_data.revlist->first);
424e77048c1Stholo 	if (log_data.revlist->last)
425e77048c1Stholo 	    free (log_data.revlist->last);
426e77048c1Stholo 	free (log_data.revlist);
427e77048c1Stholo 	log_data.revlist = rl;
428e77048c1Stholo     }
429e77048c1Stholo     while (log_data.datelist)
430e77048c1Stholo     {
431e77048c1Stholo 	struct datelist *nd = log_data.datelist->next;
432e77048c1Stholo 	if (log_data.datelist->start)
433e77048c1Stholo 	    free (log_data.datelist->start);
434e77048c1Stholo 	if (log_data.datelist->end)
435e77048c1Stholo 	    free (log_data.datelist->end);
436e77048c1Stholo 	free (log_data.datelist);
437e77048c1Stholo 	log_data.datelist = nd;
438e77048c1Stholo     }
439e77048c1Stholo     while (log_data.singledatelist)
440e77048c1Stholo     {
441e77048c1Stholo 	struct datelist *nd = log_data.singledatelist->next;
442e77048c1Stholo 	if (log_data.singledatelist->start)
443e77048c1Stholo 	    free (log_data.singledatelist->start);
444e77048c1Stholo 	if (log_data.singledatelist->end)
445e77048c1Stholo 	    free (log_data.singledatelist->end);
446e77048c1Stholo 	free (log_data.singledatelist);
447e77048c1Stholo 	log_data.singledatelist = nd;
448e77048c1Stholo     }
449e77048c1Stholo     dellist (&log_data.statelist);
450e77048c1Stholo     dellist (&log_data.authorlist);
451e77048c1Stholo 
4521e72d8d2Sderaadt     return (err);
4531e72d8d2Sderaadt }
4541e72d8d2Sderaadt 
45543c1707eStholo 
45643c1707eStholo static int
rlog_proc(argc,argv,xwhere,mwhere,mfile,shorten,local,mname,msg)45743c1707eStholo rlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
45843c1707eStholo     int argc;
45943c1707eStholo     char **argv;
46043c1707eStholo     char *xwhere;
46143c1707eStholo     char *mwhere;
46243c1707eStholo     char *mfile;
46343c1707eStholo     int shorten;
46443c1707eStholo     int local;
46543c1707eStholo     char *mname;
46643c1707eStholo     char *msg;
46743c1707eStholo {
46843c1707eStholo     /* Begin section which is identical to patch_proc--should this
46943c1707eStholo        be abstracted out somehow?  */
47043c1707eStholo     char *myargv[2];
47143c1707eStholo     int err = 0;
47243c1707eStholo     int which;
47343c1707eStholo     char *repository;
47443c1707eStholo     char *where;
47543c1707eStholo 
47643c1707eStholo     if (is_rlog)
47743c1707eStholo     {
47843c1707eStholo 	repository = xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
47943c1707eStholo 			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
48043c1707eStholo 	(void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
48143c1707eStholo 	where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
48243c1707eStholo 			 + 1);
48343c1707eStholo 	(void) strcpy (where, argv[0]);
48443c1707eStholo 
48543c1707eStholo 	/* if mfile isn't null, we need to set up to do only part of the module */
48643c1707eStholo 	if (mfile != NULL)
48743c1707eStholo 	{
48843c1707eStholo 	    char *cp;
48943c1707eStholo 	    char *path;
49043c1707eStholo 
49143c1707eStholo 	    /* if the portion of the module is a path, put the dir part on repos */
49243c1707eStholo 	    if ((cp = strrchr (mfile, '/')) != NULL)
49343c1707eStholo 	    {
49443c1707eStholo 		*cp = '\0';
49543c1707eStholo 		(void) strcat (repository, "/");
49643c1707eStholo 		(void) strcat (repository, mfile);
49743c1707eStholo 		(void) strcat (where, "/");
49843c1707eStholo 		(void) strcat (where, mfile);
49943c1707eStholo 		mfile = cp + 1;
50043c1707eStholo 	    }
50143c1707eStholo 
50243c1707eStholo 	    /* take care of the rest */
50343c1707eStholo 	    path = xmalloc (strlen (repository) + strlen (mfile) + 5);
50443c1707eStholo 	    (void) sprintf (path, "%s/%s", repository, mfile);
50543c1707eStholo 	    if (isdir (path))
50643c1707eStholo 	    {
50743c1707eStholo 		/* directory means repository gets the dir tacked on */
50843c1707eStholo 		(void) strcpy (repository, path);
50943c1707eStholo 		(void) strcat (where, "/");
51043c1707eStholo 		(void) strcat (where, mfile);
51143c1707eStholo 	    }
51243c1707eStholo 	    else
51343c1707eStholo 	    {
51443c1707eStholo 		myargv[0] = argv[0];
51543c1707eStholo 		myargv[1] = mfile;
51643c1707eStholo 		argc = 2;
51743c1707eStholo 		argv = myargv;
51843c1707eStholo 	    }
51943c1707eStholo 	    free (path);
52043c1707eStholo 	}
52143c1707eStholo 
52243c1707eStholo 	/* cd to the starting repository */
52343c1707eStholo 	if ( CVS_CHDIR (repository) < 0)
52443c1707eStholo 	{
52543c1707eStholo 	    error (0, errno, "cannot chdir to %s", repository);
52643c1707eStholo 	    free (repository);
52743c1707eStholo 	    return (1);
52843c1707eStholo 	}
52943c1707eStholo 	free (repository);
53043c1707eStholo 	/* End section which is identical to patch_proc.  */
53143c1707eStholo 
53243c1707eStholo 	which = W_REPOS | W_ATTIC;
53343c1707eStholo     }
53443c1707eStholo     else
53543c1707eStholo     {
53643c1707eStholo         where = NULL;
53743c1707eStholo         which = W_LOCAL | W_REPOS | W_ATTIC;
53843c1707eStholo     }
53943c1707eStholo 
54043c1707eStholo     err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
54143c1707eStholo 			   (DIRLEAVEPROC) NULL, (void *) &log_data,
54243c1707eStholo 			   argc - 1, argv + 1, local, which, 0, 1,
54343c1707eStholo 			   where, 1);
54443c1707eStholo     return err;
54543c1707eStholo }
54643c1707eStholo 
54743c1707eStholo 
54850bf276cStholo /*
54950bf276cStholo  * Parse a revision list specification.
55050bf276cStholo  */
55150bf276cStholo 
55250bf276cStholo static struct option_revlist *
log_parse_revlist(argstring)55350bf276cStholo log_parse_revlist (argstring)
55450bf276cStholo     const char *argstring;
55550bf276cStholo {
556e77048c1Stholo     char *orig_copy, *copy;
55750bf276cStholo     struct option_revlist *ret, **pr;
55850bf276cStholo 
55950bf276cStholo     /* Unfortunately, rlog accepts -r without an argument to mean that
56050bf276cStholo        latest revision on the default branch, so we must support that
56150bf276cStholo        for compatibility.  */
56250bf276cStholo     if (argstring == NULL)
563e77048c1Stholo 	argstring = "";
56450bf276cStholo 
56550bf276cStholo     ret = NULL;
56650bf276cStholo     pr = &ret;
56750bf276cStholo 
56850bf276cStholo     /* Copy the argument into memory so that we can change it.  We
56950bf276cStholo        don't want to change the argument because, at least as of this
570e77048c1Stholo        writing, we will use it if we send the arguments to the server.  */
571e77048c1Stholo     orig_copy = copy = xstrdup (argstring);
57250bf276cStholo     while (copy != NULL)
57350bf276cStholo     {
57450bf276cStholo 	char *comma;
57550bf276cStholo 	struct option_revlist *r;
57650bf276cStholo 
57750bf276cStholo 	comma = strchr (copy, ',');
57850bf276cStholo 	if (comma != NULL)
57950bf276cStholo 	    *comma++ = '\0';
58050bf276cStholo 
58150bf276cStholo 	r = (struct option_revlist *) xmalloc (sizeof *r);
58250bf276cStholo 	r->next = NULL;
583e77048c1Stholo 	r->first = copy;
58450bf276cStholo 	r->branchhead = 0;
585e77048c1Stholo 	r->last = strchr (copy, ':');
586e77048c1Stholo 	if (r->last != NULL)
58743c1707eStholo 	{
588e77048c1Stholo 	    *r->last++ = '\0';
58943c1707eStholo 	    r->inclusive = (*r->last != ':');
59043c1707eStholo 	    if (!r->inclusive)
59143c1707eStholo 		r->last++;
59243c1707eStholo 	}
59350bf276cStholo 	else
59450bf276cStholo 	{
595e77048c1Stholo 	    r->last = r->first;
59643c1707eStholo 	    r->inclusive = 1;
597e77048c1Stholo 	    if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
598e77048c1Stholo 	    {
59950bf276cStholo 		r->branchhead = 1;
600e77048c1Stholo 		r->first[strlen (r->first) - 1] = '\0';
60150bf276cStholo 	    }
602e77048c1Stholo 	}
603e77048c1Stholo 
604e77048c1Stholo 	if (*r->first == '\0')
605e77048c1Stholo 	    r->first = NULL;
606e77048c1Stholo 	if (*r->last == '\0')
607e77048c1Stholo 	    r->last = NULL;
608e77048c1Stholo 
609e77048c1Stholo 	if (r->first != NULL)
610e77048c1Stholo 	    r->first = xstrdup (r->first);
611e77048c1Stholo 	if (r->last != NULL)
612e77048c1Stholo 	    r->last = xstrdup (r->last);
61350bf276cStholo 
61450bf276cStholo 	*pr = r;
61550bf276cStholo 	pr = &r->next;
61650bf276cStholo 
61750bf276cStholo 	copy = comma;
61850bf276cStholo     }
61950bf276cStholo 
620e77048c1Stholo     free (orig_copy);
62150bf276cStholo     return ret;
62250bf276cStholo }
62350bf276cStholo 
62450bf276cStholo /*
62550bf276cStholo  * Parse a date specification.
62650bf276cStholo  */
62750bf276cStholo static void
log_parse_date(log_data,argstring)62850bf276cStholo log_parse_date (log_data, argstring)
62950bf276cStholo     struct log_data *log_data;
63050bf276cStholo     const char *argstring;
63150bf276cStholo {
63250bf276cStholo     char *orig_copy, *copy;
63350bf276cStholo 
63450bf276cStholo     /* Copy the argument into memory so that we can change it.  We
63550bf276cStholo        don't want to change the argument because, at least as of this
63650bf276cStholo        writing, we will use it if we send the arguments to the server.  */
637e77048c1Stholo     orig_copy = copy = xstrdup (argstring);
63850bf276cStholo     while (copy != NULL)
63950bf276cStholo     {
64050bf276cStholo 	struct datelist *nd, **pd;
64150bf276cStholo 	char *cpend, *cp, *ds, *de;
64250bf276cStholo 
64350bf276cStholo 	nd = (struct datelist *) xmalloc (sizeof *nd);
64450bf276cStholo 
64550bf276cStholo 	cpend = strchr (copy, ';');
64650bf276cStholo 	if (cpend != NULL)
64750bf276cStholo 	    *cpend++ = '\0';
64850bf276cStholo 
64950bf276cStholo 	pd = &log_data->datelist;
65050bf276cStholo 	nd->inclusive = 0;
65150bf276cStholo 
65250bf276cStholo 	if ((cp = strchr (copy, '>')) != NULL)
65350bf276cStholo 	{
65450bf276cStholo 	    *cp++ = '\0';
65550bf276cStholo 	    if (*cp == '=')
65650bf276cStholo 	    {
65750bf276cStholo 		++cp;
65850bf276cStholo 		nd->inclusive = 1;
65950bf276cStholo 	    }
66050bf276cStholo 	    ds = cp;
66150bf276cStholo 	    de = copy;
66250bf276cStholo 	}
66350bf276cStholo 	else if ((cp = strchr (copy, '<')) != NULL)
66450bf276cStholo 	{
66550bf276cStholo 	    *cp++ = '\0';
66650bf276cStholo 	    if (*cp == '=')
66750bf276cStholo 	    {
66850bf276cStholo 		++cp;
66950bf276cStholo 		nd->inclusive = 1;
67050bf276cStholo 	    }
67150bf276cStholo 	    ds = copy;
67250bf276cStholo 	    de = cp;
67350bf276cStholo 	}
67450bf276cStholo 	else
67550bf276cStholo 	{
67650bf276cStholo 	    ds = NULL;
67750bf276cStholo 	    de = copy;
67850bf276cStholo 	    pd = &log_data->singledatelist;
67950bf276cStholo 	}
68050bf276cStholo 
68150bf276cStholo 	if (ds == NULL)
68250bf276cStholo 	    nd->start = NULL;
68350bf276cStholo 	else if (*ds != '\0')
68450bf276cStholo 	    nd->start = Make_Date (ds);
68550bf276cStholo 	else
68650bf276cStholo 	{
68750bf276cStholo 	  /* 1970 was the beginning of time, as far as get_date and
688461cc63eStholo 	     Make_Date are concerned.  FIXME: That is true only if time_t
689461cc63eStholo 	     is a POSIX-style time and there is nothing in ANSI that
690461cc63eStholo 	     mandates that.  It would be cleaner to set a flag saying
691461cc63eStholo 	     whether or not there is a start date.  */
69250bf276cStholo 	    nd->start = Make_Date ("1/1/1970 UTC");
69350bf276cStholo 	}
69450bf276cStholo 
69550bf276cStholo 	if (*de != '\0')
69650bf276cStholo 	    nd->end = Make_Date (de);
69750bf276cStholo 	else
69850bf276cStholo 	{
69950bf276cStholo 	    /* We want to set the end date to some time sufficiently far
70050bf276cStholo 	       in the future to pick up all revisions that have been
70150bf276cStholo 	       created since the specified date and the time `cvs log'
702461cc63eStholo 	       completes.  FIXME: The date in question only makes sense
703461cc63eStholo 	       if time_t is a POSIX-style time and it is 32 bits
704461cc63eStholo 	       and signed.  We should instead be setting a flag saying
705461cc63eStholo 	       whether or not there is an end date.  Note that using
706461cc63eStholo 	       something like "next week" would break the testsuite (and,
707461cc63eStholo 	       perhaps less importantly, loses if the clock is set grossly
708461cc63eStholo 	       wrong).  */
709461cc63eStholo 	    nd->end = Make_Date ("2038-01-01");
71050bf276cStholo 	}
71150bf276cStholo 
71250bf276cStholo 	nd->next = *pd;
71350bf276cStholo 	*pd = nd;
71450bf276cStholo 
71550bf276cStholo 	copy = cpend;
71650bf276cStholo     }
71750bf276cStholo 
71850bf276cStholo     free (orig_copy);
71950bf276cStholo }
72050bf276cStholo 
72150bf276cStholo /*
72250bf276cStholo  * Parse a comma separated list of items, and add each one to *PLIST.
72350bf276cStholo  */
72450bf276cStholo static void
log_parse_list(plist,argstring)72550bf276cStholo log_parse_list (plist, argstring)
72650bf276cStholo     List **plist;
72750bf276cStholo     const char *argstring;
72850bf276cStholo {
72950bf276cStholo     while (1)
73050bf276cStholo     {
73150bf276cStholo 	Node *p;
73250bf276cStholo 	char *cp;
73350bf276cStholo 
73450bf276cStholo 	p = getnode ();
73550bf276cStholo 
73650bf276cStholo 	cp = strchr (argstring, ',');
73750bf276cStholo 	if (cp == NULL)
73850bf276cStholo 	    p->key = xstrdup (argstring);
73950bf276cStholo 	else
74050bf276cStholo 	{
74150bf276cStholo 	    size_t len;
74250bf276cStholo 
74350bf276cStholo 	    len = cp - argstring;
74450bf276cStholo 	    p->key = xmalloc (len + 1);
74550bf276cStholo 	    strncpy (p->key, argstring, len);
74650bf276cStholo 	    p->key[len + 1] = '\0';
74750bf276cStholo 	}
74850bf276cStholo 
74950bf276cStholo 	if (*plist == NULL)
75050bf276cStholo 	    *plist = getlist ();
75150bf276cStholo 	if (addnode (*plist, p) != 0)
75250bf276cStholo 	    freenode (p);
75350bf276cStholo 
75450bf276cStholo 	if (cp == NULL)
75550bf276cStholo 	    break;
75650bf276cStholo 
75750bf276cStholo 	argstring = cp + 1;
75850bf276cStholo     }
75950bf276cStholo }
7601e72d8d2Sderaadt 
7612286d8edStholo static int printlock_proc PROTO ((Node *, void *));
7622286d8edStholo 
7632286d8edStholo static int
printlock_proc(lock,foo)7642286d8edStholo printlock_proc (lock, foo)
7652286d8edStholo     Node *lock;
7662286d8edStholo     void *foo;
7672286d8edStholo {
7682286d8edStholo     cvs_output ("\n\t", 2);
7692286d8edStholo     cvs_output (lock->data, 0);
7702286d8edStholo     cvs_output (": ", 2);
7712286d8edStholo     cvs_output (lock->key, 0);
7722286d8edStholo     return 0;
7732286d8edStholo }
7742286d8edStholo 
7751e72d8d2Sderaadt /*
7761e72d8d2Sderaadt  * Do an rlog on a file
7771e72d8d2Sderaadt  */
7781e72d8d2Sderaadt static int
log_fileproc(callerdat,finfo)77950bf276cStholo log_fileproc (callerdat, finfo)
78050bf276cStholo     void *callerdat;
781c26070a5Stholo     struct file_info *finfo;
7821e72d8d2Sderaadt {
78350bf276cStholo     struct log_data *log_data = (struct log_data *) callerdat;
7841e72d8d2Sderaadt     Node *p;
7851e72d8d2Sderaadt     RCSNode *rcsfile;
78650bf276cStholo     char buf[50];
78750bf276cStholo     struct revlist *revlist;
78850bf276cStholo     struct log_data_and_rcs log_data_and_rcs;
7891e72d8d2Sderaadt 
790c2c61682Stholo     if ((rcsfile = finfo->rcs) == NULL)
7911e72d8d2Sderaadt     {
7921e72d8d2Sderaadt 	/* no rcs file.  What *do* we know about this file? */
793c26070a5Stholo 	p = findnode (finfo->entries, finfo->file);
7941e72d8d2Sderaadt 	if (p != NULL)
7951e72d8d2Sderaadt 	{
7961e72d8d2Sderaadt 	    Entnode *e;
7971e72d8d2Sderaadt 
7981e72d8d2Sderaadt 	    e = (Entnode *) p->data;
7992286d8edStholo 	    if (e->version[0] == '0' && e->version[1] == '\0')
8001e72d8d2Sderaadt 	    {
8011e72d8d2Sderaadt 		if (!really_quiet)
8021e72d8d2Sderaadt 		    error (0, 0, "%s has been added, but not committed",
803c26070a5Stholo 			   finfo->file);
8041e72d8d2Sderaadt 		return(0);
8051e72d8d2Sderaadt 	    }
8061e72d8d2Sderaadt 	}
8071e72d8d2Sderaadt 
8081e72d8d2Sderaadt 	if (!really_quiet)
809c26070a5Stholo 	    error (0, 0, "nothing known about %s", finfo->file);
8101e72d8d2Sderaadt 
8111e72d8d2Sderaadt 	return (1);
8121e72d8d2Sderaadt     }
8131e72d8d2Sderaadt 
81450bf276cStholo     if (log_data->nameonly)
8151e72d8d2Sderaadt     {
81650bf276cStholo 	cvs_output (rcsfile->path, 0);
81750bf276cStholo 	cvs_output ("\n", 1);
81850bf276cStholo 	return 0;
8191e72d8d2Sderaadt     }
8201e72d8d2Sderaadt 
82150bf276cStholo     /* We will need all the information in the RCS file.  */
82250bf276cStholo     RCS_fully_parse (rcsfile);
82350bf276cStholo 
82450bf276cStholo     /* Turn any symbolic revisions in the revision list into numeric
82550bf276cStholo        revisions.  */
82650bf276cStholo     revlist = log_expand_revlist (rcsfile, log_data->revlist,
82750bf276cStholo 				  log_data->default_branch);
82850bf276cStholo 
82950bf276cStholo     /* The output here is intended to be exactly compatible with the
83050bf276cStholo        output of rlog.  I'm not sure whether this code should be here
83150bf276cStholo        or in rcs.c; I put it here because it is specific to the log
83250bf276cStholo        function, even though it uses information gathered by the
83350bf276cStholo        functions in rcs.c.  */
83450bf276cStholo 
83550bf276cStholo     cvs_output ("\n", 1);
83650bf276cStholo 
83750bf276cStholo     cvs_output ("RCS file: ", 0);
83850bf276cStholo     cvs_output (rcsfile->path, 0);
83950bf276cStholo 
84043c1707eStholo     if (!is_rlog)
84143c1707eStholo     {
84250bf276cStholo 	cvs_output ("\nWorking file: ", 0);
84343c1707eStholo 	if (finfo->update_dir[0] != '\0')
8441e72d8d2Sderaadt 	{
84550bf276cStholo 	    cvs_output (finfo->update_dir, 0);
84650bf276cStholo 	    cvs_output ("/", 0);
84743c1707eStholo 	}
84850bf276cStholo 	cvs_output (finfo->file, 0);
8491e72d8d2Sderaadt     }
85050bf276cStholo 
85150bf276cStholo     cvs_output ("\nhead:", 0);
85250bf276cStholo     if (rcsfile->head != NULL)
85350bf276cStholo     {
85450bf276cStholo 	cvs_output (" ", 1);
85550bf276cStholo 	cvs_output (rcsfile->head, 0);
85650bf276cStholo     }
85750bf276cStholo 
85850bf276cStholo     cvs_output ("\nbranch:", 0);
85950bf276cStholo     if (rcsfile->branch != NULL)
86050bf276cStholo     {
86150bf276cStholo 	cvs_output (" ", 1);
86250bf276cStholo 	cvs_output (rcsfile->branch, 0);
86350bf276cStholo     }
86450bf276cStholo 
86550bf276cStholo     cvs_output ("\nlocks:", 0);
8662286d8edStholo     if (rcsfile->strict_locks)
86750bf276cStholo 	cvs_output (" strict", 0);
8682286d8edStholo     walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
86950bf276cStholo 
87050bf276cStholo     cvs_output ("\naccess list:", 0);
8712286d8edStholo     if (rcsfile->access != NULL)
87250bf276cStholo     {
87350bf276cStholo 	const char *cp;
87450bf276cStholo 
8752286d8edStholo 	cp = rcsfile->access;
87650bf276cStholo 	while (*cp != '\0')
87750bf276cStholo 	{
87850bf276cStholo 		const char *cp2;
87950bf276cStholo 
88050bf276cStholo 		cvs_output ("\n\t", 2);
88150bf276cStholo 		cp2 = cp;
882c71bc7e2Stholo 		while (! isspace ((unsigned char) *cp2) && *cp2 != '\0')
88350bf276cStholo 		    ++cp2;
88450bf276cStholo 		cvs_output (cp, cp2 - cp);
88550bf276cStholo 		cp = cp2;
886c71bc7e2Stholo 		while (isspace ((unsigned char) *cp) && *cp != '\0')
88750bf276cStholo 		    ++cp;
88850bf276cStholo 	}
88950bf276cStholo     }
89050bf276cStholo 
89150bf276cStholo     if (! log_data->notags)
89250bf276cStholo     {
89350bf276cStholo 	List *syms;
89450bf276cStholo 
89550bf276cStholo 	cvs_output ("\nsymbolic names:", 0);
89650bf276cStholo 	syms = RCS_symbols (rcsfile);
89750bf276cStholo 	walklist (syms, log_symbol, NULL);
89850bf276cStholo     }
89950bf276cStholo 
90050bf276cStholo     cvs_output ("\nkeyword substitution: ", 0);
90150bf276cStholo     if (rcsfile->expand == NULL)
90250bf276cStholo 	cvs_output ("kv", 2);
90350bf276cStholo     else
90450bf276cStholo 	cvs_output (rcsfile->expand, 0);
90550bf276cStholo 
90650bf276cStholo     cvs_output ("\ntotal revisions: ", 0);
90750bf276cStholo     sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
90850bf276cStholo     cvs_output (buf, 0);
90950bf276cStholo 
91050bf276cStholo     if (! log_data->header && ! log_data->long_header)
91150bf276cStholo     {
91250bf276cStholo 	cvs_output (";\tselected revisions: ", 0);
91350bf276cStholo 
91450bf276cStholo 	log_data_and_rcs.log_data = log_data;
91550bf276cStholo 	log_data_and_rcs.revlist = revlist;
91650bf276cStholo 	log_data_and_rcs.rcs = rcsfile;
91750bf276cStholo 
91850bf276cStholo 	/* If any single dates were specified, we need to identify the
91950bf276cStholo 	   revisions they select.  Each one selects the single
92050bf276cStholo 	   revision, which is otherwise selected, of that date or
92150bf276cStholo 	   earlier.  The log_fix_singledate routine will fill in the
92250bf276cStholo 	   start date for each specific revision.  */
92350bf276cStholo 	if (log_data->singledatelist != NULL)
92450bf276cStholo 	    walklist (rcsfile->versions, log_fix_singledate,
92550bf276cStholo 		      (void *) &log_data_and_rcs);
92650bf276cStholo 
92750bf276cStholo 	sprintf (buf, "%d", walklist (rcsfile->versions, log_count_print,
92850bf276cStholo 				      (void *) &log_data_and_rcs));
92950bf276cStholo 	cvs_output (buf, 0);
93050bf276cStholo     }
93150bf276cStholo 
93250bf276cStholo     cvs_output ("\n", 1);
93350bf276cStholo 
93450bf276cStholo     if (! log_data->header || log_data->long_header)
93550bf276cStholo     {
93650bf276cStholo 	cvs_output ("description:\n", 0);
9372286d8edStholo 	if (rcsfile->desc != NULL)
9382286d8edStholo 	    cvs_output (rcsfile->desc, 0);
93950bf276cStholo     }
94050bf276cStholo 
94150bf276cStholo     if (! log_data->header && ! log_data->long_header && rcsfile->head != NULL)
94250bf276cStholo     {
94350bf276cStholo 	p = findnode (rcsfile->versions, rcsfile->head);
94450bf276cStholo 	if (p == NULL)
94550bf276cStholo 	    error (1, 0, "can not find head revision in `%s'",
94650bf276cStholo 		   finfo->fullname);
94750bf276cStholo 	while (p != NULL)
94850bf276cStholo 	{
94950bf276cStholo 	    RCSVers *vers;
95050bf276cStholo 
95150bf276cStholo 	    vers = (RCSVers *) p->data;
95250bf276cStholo 	    log_version (log_data, revlist, rcsfile, vers, 1);
95350bf276cStholo 	    if (vers->next == NULL)
95450bf276cStholo 		p = NULL;
95550bf276cStholo 	    else
95650bf276cStholo 	    {
95750bf276cStholo 		p = findnode (rcsfile->versions, vers->next);
95850bf276cStholo 		if (p == NULL)
95950bf276cStholo 		    error (1, 0, "can not find next revision `%s' in `%s'",
96050bf276cStholo 			   vers->next, finfo->fullname);
96150bf276cStholo 	    }
96250bf276cStholo 	}
96350bf276cStholo 
96450bf276cStholo 	log_tree (log_data, revlist, rcsfile, rcsfile->head);
96550bf276cStholo     }
96650bf276cStholo 
96750bf276cStholo     cvs_output("\
96850bf276cStholo =============================================================================\n",
96950bf276cStholo 	       0);
97050bf276cStholo 
97150bf276cStholo     /* Free up the new revlist and restore the old one.  */
97250bf276cStholo     log_free_revlist (revlist);
97350bf276cStholo 
97450bf276cStholo     /* If singledatelist is not NULL, free up the start dates we added
97550bf276cStholo        to it.  */
97650bf276cStholo     if (log_data->singledatelist != NULL)
97750bf276cStholo     {
97850bf276cStholo 	struct datelist *d;
97950bf276cStholo 
98050bf276cStholo 	for (d = log_data->singledatelist; d != NULL; d = d->next)
98150bf276cStholo 	{
98250bf276cStholo 	    if (d->start != NULL)
98350bf276cStholo 		free (d->start);
98450bf276cStholo 	    d->start = NULL;
98550bf276cStholo 	}
98650bf276cStholo     }
98750bf276cStholo 
98850bf276cStholo     return 0;
98950bf276cStholo }
99050bf276cStholo 
99150bf276cStholo /*
99250bf276cStholo  * Fix up a revision list in order to compare it against versions.
99350bf276cStholo  * Expand any symbolic revisions.
99450bf276cStholo  */
99550bf276cStholo static struct revlist *
log_expand_revlist(rcs,revlist,default_branch)99650bf276cStholo log_expand_revlist (rcs, revlist, default_branch)
99750bf276cStholo     RCSNode *rcs;
99850bf276cStholo     struct option_revlist *revlist;
99950bf276cStholo     int default_branch;
100050bf276cStholo {
100150bf276cStholo     struct option_revlist *r;
100250bf276cStholo     struct revlist *ret, **pr;
100350bf276cStholo 
100450bf276cStholo     ret = NULL;
100550bf276cStholo     pr = &ret;
100650bf276cStholo     for (r = revlist; r != NULL; r = r->next)
100750bf276cStholo     {
100850bf276cStholo 	struct revlist *nr;
100950bf276cStholo 
101050bf276cStholo 	nr = (struct revlist *) xmalloc (sizeof *nr);
101143c1707eStholo 	nr->inclusive = r->inclusive;
101250bf276cStholo 
101350bf276cStholo 	if (r->first == NULL && r->last == NULL)
101450bf276cStholo 	{
101550bf276cStholo 	    /* If both first and last are NULL, it means that we want
101650bf276cStholo 	       just the head of the default branch, which is RCS_head.  */
101750bf276cStholo 	    nr->first = RCS_head (rcs);
101850bf276cStholo 	    nr->last = xstrdup (nr->first);
101950bf276cStholo 	    nr->fields = numdots (nr->first) + 1;
102050bf276cStholo 	}
102150bf276cStholo 	else if (r->branchhead)
102250bf276cStholo 	{
102350bf276cStholo 	    char *branch;
102450bf276cStholo 
102550bf276cStholo 	    /* Print just the head of the branch.  */
1026c71bc7e2Stholo 	    if (isdigit ((unsigned char) r->first[0]))
102750bf276cStholo 		nr->first = RCS_getbranch (rcs, r->first, 1);
102850bf276cStholo 	    else
102950bf276cStholo 	    {
103050bf276cStholo 		branch = RCS_whatbranch (rcs, r->first);
103150bf276cStholo 		if (branch == NULL)
103250bf276cStholo 		{
103350bf276cStholo 		    error (0, 0, "warning: `%s' is not a branch in `%s'",
103450bf276cStholo 			   r->first, rcs->path);
103550bf276cStholo 		    free (nr);
103650bf276cStholo 		    continue;
103750bf276cStholo 		}
103850bf276cStholo 		nr->first = RCS_getbranch (rcs, branch, 1);
103950bf276cStholo 		free (branch);
104050bf276cStholo 	    }
104150bf276cStholo 	    if (nr->first == NULL)
104250bf276cStholo 	    {
104350bf276cStholo 		error (0, 0, "warning: no revision `%s' in `%s'",
104450bf276cStholo 		       r->first, rcs->path);
104550bf276cStholo 		free (nr);
104650bf276cStholo 		continue;
104750bf276cStholo 	    }
104850bf276cStholo 	    nr->last = xstrdup (nr->first);
104950bf276cStholo 	    nr->fields = numdots (nr->first) + 1;
105050bf276cStholo 	}
105150bf276cStholo 	else
105250bf276cStholo 	{
1053c71bc7e2Stholo 	    if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
105450bf276cStholo 		nr->first = xstrdup (r->first);
105550bf276cStholo 	    else
105650bf276cStholo 	    {
105750bf276cStholo 		if (RCS_nodeisbranch (rcs, r->first))
105850bf276cStholo 		    nr->first = RCS_whatbranch (rcs, r->first);
105950bf276cStholo 		else
106050bf276cStholo 		    nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
106150bf276cStholo 		if (nr->first == NULL)
106250bf276cStholo 		{
106350bf276cStholo 		    error (0, 0, "warning: no revision `%s' in `%s'",
106450bf276cStholo 			   r->first, rcs->path);
106550bf276cStholo 		    free (nr);
106650bf276cStholo 		    continue;
106750bf276cStholo 		}
106850bf276cStholo 	    }
106950bf276cStholo 
107050bf276cStholo 	    if (r->last == r->first)
107150bf276cStholo 		nr->last = xstrdup (nr->first);
1072c71bc7e2Stholo 	    else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
107350bf276cStholo 		nr->last = xstrdup (r->last);
107450bf276cStholo 	    else
107550bf276cStholo 	    {
107650bf276cStholo 		if (RCS_nodeisbranch (rcs, r->last))
107750bf276cStholo 		    nr->last = RCS_whatbranch (rcs, r->last);
107850bf276cStholo 		else
107950bf276cStholo 		    nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
108050bf276cStholo 		if (nr->last == NULL)
108150bf276cStholo 		{
108250bf276cStholo 		    error (0, 0, "warning: no revision `%s' in `%s'",
108350bf276cStholo 			   r->last, rcs->path);
108450bf276cStholo 		    if (nr->first != NULL)
108550bf276cStholo 			free (nr->first);
108650bf276cStholo 		    free (nr);
108750bf276cStholo 		    continue;
108850bf276cStholo 		}
108950bf276cStholo 	    }
109050bf276cStholo 
109150bf276cStholo 	    /* Process the revision numbers the same way that rlog
109250bf276cStholo                does.  This code is a bit cryptic for my tastes, but
109350bf276cStholo                keeping the same implementation as rlog ensures a
109450bf276cStholo                certain degree of compatibility.  */
109550bf276cStholo 	    if (r->first == NULL)
109650bf276cStholo 	    {
109750bf276cStholo 		nr->fields = numdots (nr->last) + 1;
109850bf276cStholo 		if (nr->fields < 2)
109950bf276cStholo 		    nr->first = xstrdup (".0");
110050bf276cStholo 		else
110150bf276cStholo 		{
110250bf276cStholo 		    char *cp;
110350bf276cStholo 
110450bf276cStholo 		    nr->first = xstrdup (nr->last);
110550bf276cStholo 		    cp = strrchr (nr->first, '.');
110650bf276cStholo 		    strcpy (cp, ".0");
110750bf276cStholo 		}
110850bf276cStholo 	    }
110950bf276cStholo 	    else if (r->last == NULL)
111050bf276cStholo 	    {
111150bf276cStholo 		nr->fields = numdots (nr->first) + 1;
111250bf276cStholo 		nr->last = xstrdup (nr->first);
111350bf276cStholo 		if (nr->fields < 2)
111450bf276cStholo 		    nr->last[0] = '\0';
111550bf276cStholo 		else
111650bf276cStholo 		{
111750bf276cStholo 		    char *cp;
111850bf276cStholo 
111950bf276cStholo 		    cp = strrchr (nr->last, '.');
112050bf276cStholo 		    *cp = '\0';
112150bf276cStholo 		}
112250bf276cStholo 	    }
112350bf276cStholo 	    else
112450bf276cStholo 	    {
112550bf276cStholo 		nr->fields = numdots (nr->first) + 1;
112650bf276cStholo 		if (nr->fields != numdots (nr->last) + 1
112750bf276cStholo 		    || (nr->fields > 2
112850bf276cStholo 			&& version_compare (nr->first, nr->last,
112950bf276cStholo 					    nr->fields - 1) != 0))
113050bf276cStholo 		{
113150bf276cStholo 		    error (0, 0,
113250bf276cStholo 			   "invalid branch or revision pair %s:%s in `%s'",
113350bf276cStholo 			   r->first, r->last, rcs->path);
113450bf276cStholo 		    free (nr->first);
113550bf276cStholo 		    free (nr->last);
113650bf276cStholo 		    free (nr);
113750bf276cStholo 		    continue;
113850bf276cStholo 		}
113950bf276cStholo 		if (version_compare (nr->first, nr->last, nr->fields) > 0)
114050bf276cStholo 		{
114150bf276cStholo 		    char *tmp;
114250bf276cStholo 
114350bf276cStholo 		    tmp = nr->first;
114450bf276cStholo 		    nr->first = nr->last;
114550bf276cStholo 		    nr->last = tmp;
114650bf276cStholo 		}
114750bf276cStholo 	    }
114850bf276cStholo 	}
114950bf276cStholo 
115050bf276cStholo 	nr->next = NULL;
115150bf276cStholo 	*pr = nr;
115250bf276cStholo 	pr = &nr->next;
115350bf276cStholo     }
115450bf276cStholo 
115550bf276cStholo     /* If the default branch was requested, add a revlist entry for
115650bf276cStholo        it.  This is how rlog handles this option.  */
115750bf276cStholo     if (default_branch
115850bf276cStholo 	&& (rcs->head != NULL || rcs->branch != NULL))
115950bf276cStholo     {
116050bf276cStholo 	struct revlist *nr;
116150bf276cStholo 
116250bf276cStholo 	nr = (struct revlist *) xmalloc (sizeof *nr);
116350bf276cStholo 	if (rcs->branch != NULL)
116450bf276cStholo 	    nr->first = xstrdup (rcs->branch);
116550bf276cStholo 	else
116650bf276cStholo 	{
116750bf276cStholo 	    char *cp;
116850bf276cStholo 
116950bf276cStholo 	    nr->first = xstrdup (rcs->head);
117050bf276cStholo 	    cp = strrchr (nr->first, '.');
117150bf276cStholo 	    *cp = '\0';
117250bf276cStholo 	}
117350bf276cStholo 	nr->last = xstrdup (nr->first);
117450bf276cStholo 	nr->fields = numdots (nr->first) + 1;
117543c1707eStholo 	nr->inclusive = 1;
117650bf276cStholo 
117750bf276cStholo 	nr->next = NULL;
117850bf276cStholo 	*pr = nr;
117950bf276cStholo     }
118050bf276cStholo 
118150bf276cStholo     return ret;
118250bf276cStholo }
118350bf276cStholo 
118450bf276cStholo /*
118550bf276cStholo  * Free a revlist created by log_expand_revlist.
118650bf276cStholo  */
118750bf276cStholo static void
log_free_revlist(revlist)118850bf276cStholo log_free_revlist (revlist)
118950bf276cStholo     struct revlist *revlist;
119050bf276cStholo {
119150bf276cStholo     struct revlist *r;
119250bf276cStholo 
119350bf276cStholo     r = revlist;
119450bf276cStholo     while (r != NULL)
119550bf276cStholo     {
119650bf276cStholo 	struct revlist *next;
119750bf276cStholo 
119850bf276cStholo 	if (r->first != NULL)
119950bf276cStholo 	    free (r->first);
120050bf276cStholo 	if (r->last != NULL)
120150bf276cStholo 	    free (r->last);
120250bf276cStholo 	next = r->next;
120350bf276cStholo 	free (r);
120450bf276cStholo 	r = next;
120550bf276cStholo     }
120650bf276cStholo }
120750bf276cStholo 
120850bf276cStholo /*
120950bf276cStholo  * Return nonzero if a revision should be printed, based on the
121050bf276cStholo  * options provided.
121150bf276cStholo  */
121250bf276cStholo static int
log_version_requested(log_data,revlist,rcs,vnode)121350bf276cStholo log_version_requested (log_data, revlist, rcs, vnode)
121450bf276cStholo     struct log_data *log_data;
121550bf276cStholo     struct revlist *revlist;
121650bf276cStholo     RCSNode *rcs;
121750bf276cStholo     RCSVers *vnode;
121850bf276cStholo {
121950bf276cStholo     /* Handle the list of states from the -s option.  */
1220b6c02222Stholo     if (log_data->statelist != NULL
1221b6c02222Stholo 	&& findnode (log_data->statelist, vnode->state) == NULL)
122250bf276cStholo     {
122350bf276cStholo 	return 0;
122450bf276cStholo     }
122550bf276cStholo 
122650bf276cStholo     /* Handle the list of authors from the -w option.  */
122750bf276cStholo     if (log_data->authorlist != NULL)
122850bf276cStholo     {
122950bf276cStholo 	if (vnode->author != NULL
123050bf276cStholo 	    && findnode (log_data->authorlist, vnode->author) == NULL)
123150bf276cStholo 	{
123250bf276cStholo 	    return 0;
123350bf276cStholo 	}
123450bf276cStholo     }
123550bf276cStholo 
123650bf276cStholo     /* rlog considers all the -d options together when it decides
123750bf276cStholo        whether to print a revision, so we must be compatible.  */
123850bf276cStholo     if (log_data->datelist != NULL || log_data->singledatelist != NULL)
123950bf276cStholo     {
124050bf276cStholo 	struct datelist *d;
124150bf276cStholo 
124250bf276cStholo 	for (d = log_data->datelist; d != NULL; d = d->next)
124350bf276cStholo 	{
124450bf276cStholo 	    int cmp;
124550bf276cStholo 
124650bf276cStholo 	    cmp = RCS_datecmp (vnode->date, d->start);
124750bf276cStholo 	    if (cmp > 0 || (cmp == 0 && d->inclusive))
124850bf276cStholo 	    {
124950bf276cStholo 		cmp = RCS_datecmp (vnode->date, d->end);
125050bf276cStholo 		if (cmp < 0 || (cmp == 0 && d->inclusive))
125150bf276cStholo 		    break;
125250bf276cStholo 	    }
125350bf276cStholo 	}
125450bf276cStholo 
125550bf276cStholo 	if (d == NULL)
125650bf276cStholo 	{
125750bf276cStholo 	    /* Look through the list of specific dates.  We want to
125850bf276cStholo 	       select the revision with the exact date found in the
125950bf276cStholo 	       start field.  The commit code ensures that it is
126050bf276cStholo 	       impossible to check in multiple revisions of a single
126150bf276cStholo 	       file in a single second, so checking the date this way
126250bf276cStholo 	       should never select more than one revision.  */
126350bf276cStholo 	    for (d = log_data->singledatelist; d != NULL; d = d->next)
126450bf276cStholo 	    {
126550bf276cStholo 		if (d->start != NULL
126650bf276cStholo 		    && RCS_datecmp (vnode->date, d->start) == 0)
126750bf276cStholo 		{
126850bf276cStholo 		    break;
126950bf276cStholo 		}
127050bf276cStholo 	    }
127150bf276cStholo 
127250bf276cStholo 	    if (d == NULL)
127350bf276cStholo 		return 0;
127450bf276cStholo 	}
127550bf276cStholo     }
127650bf276cStholo 
127750bf276cStholo     /* If the -r or -b options were used, REVLIST will be non NULL,
127850bf276cStholo        and we print the union of the specified revisions.  */
127950bf276cStholo     if (revlist != NULL)
128050bf276cStholo     {
128150bf276cStholo 	char *v;
128250bf276cStholo 	int vfields;
128350bf276cStholo 	struct revlist *r;
128450bf276cStholo 
128550bf276cStholo 	/* This code is taken from rlog.  */
128650bf276cStholo 	v = vnode->version;
128750bf276cStholo 	vfields = numdots (v) + 1;
128850bf276cStholo 	for (r = revlist; r != NULL; r = r->next)
128950bf276cStholo 	{
129043c1707eStholo 	    if (vfields == r->fields + (r->fields & 1) &&
129143c1707eStholo 		(r->inclusive ?
129243c1707eStholo 		    version_compare (v, r->first, r->fields) >= 0
129343c1707eStholo 		    && version_compare (v, r->last, r->fields) <= 0 :
129443c1707eStholo 		    version_compare (v, r->first, r->fields) > 0
129543c1707eStholo 		    && version_compare (v, r->last, r->fields) < 0))
129650bf276cStholo 	    {
129750bf276cStholo 		return 1;
129850bf276cStholo 	    }
129950bf276cStholo 	}
130050bf276cStholo 
130150bf276cStholo 	/* If we get here, then the -b and/or the -r option was used,
130250bf276cStholo            but did not match this revision, so we reject it.  */
130350bf276cStholo 
130450bf276cStholo 	return 0;
130550bf276cStholo     }
130650bf276cStholo 
130750bf276cStholo     /* By default, we print all revisions.  */
130850bf276cStholo     return 1;
130950bf276cStholo }
131050bf276cStholo 
131150bf276cStholo /*
131250bf276cStholo  * Output a single symbol.  This is called via walklist.
131350bf276cStholo  */
131450bf276cStholo /*ARGSUSED*/
131550bf276cStholo static int
log_symbol(p,closure)131650bf276cStholo log_symbol (p, closure)
131750bf276cStholo     Node *p;
131850bf276cStholo     void *closure;
131950bf276cStholo {
132050bf276cStholo     cvs_output ("\n\t", 2);
132150bf276cStholo     cvs_output (p->key, 0);
132250bf276cStholo     cvs_output (": ", 2);
132350bf276cStholo     cvs_output (p->data, 0);
132450bf276cStholo     return 0;
132550bf276cStholo }
132650bf276cStholo 
132750bf276cStholo /*
132850bf276cStholo  * Count the number of entries on a list.  This is called via walklist.
132950bf276cStholo  */
133050bf276cStholo /*ARGSUSED*/
133150bf276cStholo static int
log_count(p,closure)133250bf276cStholo log_count (p, closure)
133350bf276cStholo     Node *p;
133450bf276cStholo     void *closure;
133550bf276cStholo {
133650bf276cStholo     return 1;
133750bf276cStholo }
133850bf276cStholo 
133950bf276cStholo /*
134050bf276cStholo  * Sort out a single date specification by narrowing down the date
134150bf276cStholo  * until we find the specific selected revision.
134250bf276cStholo  */
134350bf276cStholo static int
log_fix_singledate(p,closure)134450bf276cStholo log_fix_singledate (p, closure)
134550bf276cStholo     Node *p;
134650bf276cStholo     void *closure;
134750bf276cStholo {
134850bf276cStholo     struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
134950bf276cStholo     Node *pv;
135050bf276cStholo     RCSVers *vnode;
135150bf276cStholo     struct datelist *holdsingle, *holddate;
135250bf276cStholo     int requested;
135350bf276cStholo 
135450bf276cStholo     pv = findnode (data->rcs->versions, p->key);
135550bf276cStholo     if (pv == NULL)
135650bf276cStholo 	error (1, 0, "missing version `%s' in RCS file `%s'",
135750bf276cStholo 	       p->key, data->rcs->path);
135850bf276cStholo     vnode = (RCSVers *) pv->data;
135950bf276cStholo 
136050bf276cStholo     /* We are only interested if this revision passes any other tests.
136150bf276cStholo        Temporarily clear log_data->singledatelist to avoid confusing
136250bf276cStholo        log_version_requested.  We also clear log_data->datelist,
136350bf276cStholo        because rlog considers all the -d options together.  We don't
136450bf276cStholo        want to reject a revision because it does not match a date pair
136550bf276cStholo        if we are going to select it on the basis of the singledate.  */
136650bf276cStholo     holdsingle = data->log_data->singledatelist;
136750bf276cStholo     data->log_data->singledatelist = NULL;
136850bf276cStholo     holddate = data->log_data->datelist;
136950bf276cStholo     data->log_data->datelist = NULL;
137050bf276cStholo     requested = log_version_requested (data->log_data, data->revlist,
137150bf276cStholo 				       data->rcs, vnode);
137250bf276cStholo     data->log_data->singledatelist = holdsingle;
137350bf276cStholo     data->log_data->datelist = holddate;
137450bf276cStholo 
137550bf276cStholo     if (requested)
137650bf276cStholo     {
137750bf276cStholo 	struct datelist *d;
137850bf276cStholo 
137950bf276cStholo 	/* For each single date, if this revision is before the
138050bf276cStholo 	   specified date, but is closer than the previously selected
138150bf276cStholo 	   revision, select it instead.  */
138250bf276cStholo 	for (d = data->log_data->singledatelist; d != NULL; d = d->next)
138350bf276cStholo 	{
138450bf276cStholo 	    if (RCS_datecmp (vnode->date, d->end) <= 0
138550bf276cStholo 		&& (d->start == NULL
138650bf276cStholo 		    || RCS_datecmp (vnode->date, d->start) > 0))
138750bf276cStholo 	    {
138850bf276cStholo 		if (d->start != NULL)
138950bf276cStholo 		    free (d->start);
139050bf276cStholo 		d->start = xstrdup (vnode->date);
139150bf276cStholo 	    }
139250bf276cStholo 	}
139350bf276cStholo     }
139450bf276cStholo 
139550bf276cStholo     return 0;
139650bf276cStholo }
139750bf276cStholo 
139850bf276cStholo /*
139950bf276cStholo  * Count the number of revisions we are going to print.
140050bf276cStholo  */
140150bf276cStholo static int
log_count_print(p,closure)140250bf276cStholo log_count_print (p, closure)
140350bf276cStholo     Node *p;
140450bf276cStholo     void *closure;
140550bf276cStholo {
140650bf276cStholo     struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
140750bf276cStholo     Node *pv;
140850bf276cStholo 
140950bf276cStholo     pv = findnode (data->rcs->versions, p->key);
141050bf276cStholo     if (pv == NULL)
141150bf276cStholo 	error (1, 0, "missing version `%s' in RCS file `%s'",
141250bf276cStholo 	       p->key, data->rcs->path);
141350bf276cStholo     if (log_version_requested (data->log_data, data->revlist, data->rcs,
141450bf276cStholo 			       (RCSVers *) pv->data))
141550bf276cStholo 	return 1;
141650bf276cStholo     else
141750bf276cStholo 	return 0;
141850bf276cStholo }
141950bf276cStholo 
142050bf276cStholo /*
142150bf276cStholo  * Print the list of changes, not including the trunk, in reverse
142250bf276cStholo  * order for each branch.
142350bf276cStholo  */
142450bf276cStholo static void
log_tree(log_data,revlist,rcs,ver)142550bf276cStholo log_tree (log_data, revlist, rcs, ver)
142650bf276cStholo     struct log_data *log_data;
142750bf276cStholo     struct revlist *revlist;
142850bf276cStholo     RCSNode *rcs;
142950bf276cStholo     const char *ver;
143050bf276cStholo {
143150bf276cStholo     Node *p;
143250bf276cStholo     RCSVers *vnode;
143350bf276cStholo 
143450bf276cStholo     p = findnode (rcs->versions, ver);
143550bf276cStholo     if (p == NULL)
143650bf276cStholo 	error (1, 0, "missing version `%s' in RCS file `%s'",
143750bf276cStholo 	       ver, rcs->path);
143850bf276cStholo     vnode = (RCSVers *) p->data;
143950bf276cStholo     if (vnode->next != NULL)
144050bf276cStholo 	log_tree (log_data, revlist, rcs, vnode->next);
144150bf276cStholo     if (vnode->branches != NULL)
144250bf276cStholo     {
144350bf276cStholo 	Node *head, *branch;
144450bf276cStholo 
144550bf276cStholo 	/* We need to do the branches in reverse order.  This breaks
144650bf276cStholo            the List abstraction, but so does most of the branch
144750bf276cStholo            manipulation in rcs.c.  */
144850bf276cStholo 	head = vnode->branches->list;
144950bf276cStholo 	for (branch = head->prev; branch != head; branch = branch->prev)
145050bf276cStholo 	{
145150bf276cStholo 	    log_abranch (log_data, revlist, rcs, branch->key);
145250bf276cStholo 	    log_tree (log_data, revlist, rcs, branch->key);
145350bf276cStholo 	}
145450bf276cStholo     }
145550bf276cStholo }
145650bf276cStholo 
145750bf276cStholo /*
145850bf276cStholo  * Log the changes for a branch, in reverse order.
145950bf276cStholo  */
146050bf276cStholo static void
log_abranch(log_data,revlist,rcs,ver)146150bf276cStholo log_abranch (log_data, revlist, rcs, ver)
146250bf276cStholo     struct log_data *log_data;
146350bf276cStholo     struct revlist *revlist;
146450bf276cStholo     RCSNode *rcs;
146550bf276cStholo     const char *ver;
146650bf276cStholo {
146750bf276cStholo     Node *p;
146850bf276cStholo     RCSVers *vnode;
146950bf276cStholo 
147050bf276cStholo     p = findnode (rcs->versions, ver);
147150bf276cStholo     if (p == NULL)
147250bf276cStholo 	error (1, 0, "missing version `%s' in RCS file `%s'",
147350bf276cStholo 	       ver, rcs->path);
147450bf276cStholo     vnode = (RCSVers *) p->data;
147550bf276cStholo     if (vnode->next != NULL)
147650bf276cStholo 	log_abranch (log_data, revlist, rcs, vnode->next);
147750bf276cStholo     log_version (log_data, revlist, rcs, vnode, 0);
147850bf276cStholo }
147950bf276cStholo 
148050bf276cStholo /*
148150bf276cStholo  * Print the log output for a single version.
148250bf276cStholo  */
148350bf276cStholo static void
log_version(log_data,revlist,rcs,ver,trunk)148450bf276cStholo log_version (log_data, revlist, rcs, ver, trunk)
148550bf276cStholo     struct log_data *log_data;
148650bf276cStholo     struct revlist *revlist;
148750bf276cStholo     RCSNode *rcs;
148850bf276cStholo     RCSVers *ver;
148950bf276cStholo     int trunk;
149050bf276cStholo {
149150bf276cStholo     Node *p;
149250bf276cStholo     int year, mon, mday, hour, min, sec;
149350bf276cStholo     char buf[100];
149450bf276cStholo     Node *padd, *pdel;
149550bf276cStholo 
149650bf276cStholo     if (! log_version_requested (log_data, revlist, rcs, ver))
149750bf276cStholo 	return;
149850bf276cStholo 
149950bf276cStholo     cvs_output ("----------------------------\nrevision ", 0);
150050bf276cStholo     cvs_output (ver->version, 0);
150150bf276cStholo 
15022286d8edStholo     p = findnode (RCS_getlocks (rcs), ver->version);
150350bf276cStholo     if (p != NULL)
150450bf276cStholo     {
150550bf276cStholo 	cvs_output ("\tlocked by: ", 0);
150650bf276cStholo 	cvs_output (p->data, 0);
150750bf276cStholo 	cvs_output (";", 1);
150850bf276cStholo     }
150950bf276cStholo 
151050bf276cStholo     cvs_output ("\ndate: ", 0);
151150bf276cStholo     (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
151250bf276cStholo 		   &sec);
151350bf276cStholo     if (year < 1900)
151450bf276cStholo 	year += 1900;
151550bf276cStholo     sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday,
151650bf276cStholo 	     hour, min, sec);
151750bf276cStholo     cvs_output (buf, 0);
151850bf276cStholo 
151950bf276cStholo     cvs_output (";  author: ", 0);
152050bf276cStholo     cvs_output (ver->author, 0);
152150bf276cStholo 
152250bf276cStholo     cvs_output (";  state: ", 0);
1523b6c02222Stholo     cvs_output (ver->state, 0);
152450bf276cStholo     cvs_output (";", 1);
152550bf276cStholo 
152650bf276cStholo     if (! trunk)
152750bf276cStholo     {
152850bf276cStholo 	padd = findnode (ver->other, ";add");
152950bf276cStholo 	pdel = findnode (ver->other, ";delete");
153050bf276cStholo     }
153150bf276cStholo     else if (ver->next == NULL)
153250bf276cStholo     {
153350bf276cStholo 	padd = NULL;
153450bf276cStholo 	pdel = NULL;
153550bf276cStholo     }
153650bf276cStholo     else
153750bf276cStholo     {
153850bf276cStholo 	Node *nextp;
153950bf276cStholo 	RCSVers *nextver;
154050bf276cStholo 
154150bf276cStholo 	nextp = findnode (rcs->versions, ver->next);
154250bf276cStholo 	if (nextp == NULL)
154350bf276cStholo 	    error (1, 0, "missing version `%s' in `%s'", ver->next,
154450bf276cStholo 		   rcs->path);
154550bf276cStholo 	nextver = (RCSVers *) nextp->data;
154650bf276cStholo 	pdel = findnode (nextver->other, ";add");
154750bf276cStholo 	padd = findnode (nextver->other, ";delete");
154850bf276cStholo     }
154950bf276cStholo 
155050bf276cStholo     if (padd != NULL)
155150bf276cStholo     {
155250bf276cStholo 	cvs_output ("  lines: +", 0);
155350bf276cStholo 	cvs_output (padd->data, 0);
155450bf276cStholo 	cvs_output (" -", 2);
155550bf276cStholo 	cvs_output (pdel->data, 0);
1556*c133e2caSjcs 	cvs_output (";", 1);
1557*c133e2caSjcs     }
1558*c133e2caSjcs 
1559*c133e2caSjcs     p = findnode (ver->other_delta,"commitid");
1560*c133e2caSjcs     if (p != NULL && p->data)
1561*c133e2caSjcs     {
1562*c133e2caSjcs 	cvs_output ("  commitid: ", 12);
1563*c133e2caSjcs 	cvs_output (p->data, 0);
1564*c133e2caSjcs 	cvs_output (";", 1);
156550bf276cStholo     }
156650bf276cStholo 
156750bf276cStholo     if (ver->branches != NULL)
156850bf276cStholo     {
156950bf276cStholo 	cvs_output ("\nbranches:", 0);
157050bf276cStholo 	walklist (ver->branches, log_branch, (void *) NULL);
157150bf276cStholo     }
157250bf276cStholo 
157350bf276cStholo     cvs_output ("\n", 1);
157450bf276cStholo 
157550bf276cStholo     p = findnode (ver->other, "log");
15762286d8edStholo     /* The p->date == NULL case is the normal one for an empty log
15772286d8edStholo        message (rcs-14 in sanity.sh).  I don't think the case where
15782286d8edStholo        p->data is "" can happen (getrcskey in rcs.c checks for an
15792286d8edStholo        empty string and set the value to NULL in that case).  My guess
15802286d8edStholo        would be the p == NULL case would mean an RCS file which was
15812286d8edStholo        missing the "log" keyword (which is illegal according to
15822286d8edStholo        rcsfile.5).  */
15832286d8edStholo     if (p == NULL || p->data == NULL || p->data[0] == '\0')
158450bf276cStholo 	cvs_output ("*** empty log message ***\n", 0);
158550bf276cStholo     else
158650bf276cStholo     {
158750bf276cStholo 	/* FIXME: Technically, the log message could contain a null
158850bf276cStholo            byte.  */
158950bf276cStholo 	cvs_output (p->data, 0);
159050bf276cStholo 	if (p->data[strlen (p->data) - 1] != '\n')
159150bf276cStholo 	    cvs_output ("\n", 1);
159250bf276cStholo     }
159350bf276cStholo }
159450bf276cStholo 
159550bf276cStholo /*
159650bf276cStholo  * Output a branch version.  This is called via walklist.
159750bf276cStholo  */
159850bf276cStholo /*ARGSUSED*/
159950bf276cStholo static int
log_branch(p,closure)160050bf276cStholo log_branch (p, closure)
160150bf276cStholo     Node *p;
160250bf276cStholo     void *closure;
160350bf276cStholo {
160450bf276cStholo     cvs_output ("  ", 2);
160550bf276cStholo     if ((numdots (p->key) & 1) == 0)
160650bf276cStholo 	cvs_output (p->key, 0);
160750bf276cStholo     else
160850bf276cStholo     {
160950bf276cStholo 	char *f, *cp;
161050bf276cStholo 
161150bf276cStholo 	f = xstrdup (p->key);
161250bf276cStholo 	cp = strrchr (f, '.');
161350bf276cStholo 	*cp = '\0';
161450bf276cStholo 	cvs_output (f, 0);
161550bf276cStholo 	free (f);
161650bf276cStholo     }
161750bf276cStholo     cvs_output (";", 1);
161850bf276cStholo     return 0;
16191e72d8d2Sderaadt }
16201e72d8d2Sderaadt 
16211e72d8d2Sderaadt /*
16221e72d8d2Sderaadt  * Print a warm fuzzy message
16231e72d8d2Sderaadt  */
16241e72d8d2Sderaadt /* ARGSUSED */
16251e72d8d2Sderaadt static Dtype
log_dirproc(callerdat,dir,repository,update_dir,entries)162650bf276cStholo log_dirproc (callerdat, dir, repository, update_dir, entries)
162750bf276cStholo     void *callerdat;
16281e72d8d2Sderaadt     char *dir;
16291e72d8d2Sderaadt     char *repository;
16301e72d8d2Sderaadt     char *update_dir;
163150bf276cStholo     List *entries;
16321e72d8d2Sderaadt {
16331e72d8d2Sderaadt     if (!isdir (dir))
16341e72d8d2Sderaadt 	return (R_SKIP_ALL);
16351e72d8d2Sderaadt 
16361e72d8d2Sderaadt     if (!quiet)
16371e72d8d2Sderaadt 	error (0, 0, "Logging %s", update_dir);
16381e72d8d2Sderaadt     return (R_PROCESS);
16391e72d8d2Sderaadt }
164050bf276cStholo 
164150bf276cStholo /*
164250bf276cStholo  * Compare versions.  This is taken from RCS compartial.
164350bf276cStholo  */
164450bf276cStholo static int
version_compare(v1,v2,len)164550bf276cStholo version_compare (v1, v2, len)
164650bf276cStholo     const char *v1;
164750bf276cStholo     const char *v2;
164850bf276cStholo     int len;
164950bf276cStholo {
165050bf276cStholo     while (1)
165150bf276cStholo     {
165250bf276cStholo 	int d1, d2, r;
165350bf276cStholo 
165450bf276cStholo 	if (*v1 == '\0')
165550bf276cStholo 	    return 1;
165650bf276cStholo 	if (*v2 == '\0')
165750bf276cStholo 	    return -1;
165850bf276cStholo 
165950bf276cStholo 	while (*v1 == '0')
166050bf276cStholo 	    ++v1;
1661c71bc7e2Stholo 	for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
166250bf276cStholo 	    ;
166350bf276cStholo 
166450bf276cStholo 	while (*v2 == '0')
166550bf276cStholo 	    ++v2;
1666c71bc7e2Stholo 	for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
166750bf276cStholo 	    ;
166850bf276cStholo 
166950bf276cStholo 	if (d1 != d2)
167050bf276cStholo 	    return d1 < d2 ? -1 : 1;
167150bf276cStholo 
167250bf276cStholo 	r = memcmp (v1, v2, d1);
167350bf276cStholo 	if (r != 0)
167450bf276cStholo 	    return r;
167550bf276cStholo 
167650bf276cStholo 	--len;
167750bf276cStholo 	if (len == 0)
167850bf276cStholo 	    return 0;
167950bf276cStholo 
168050bf276cStholo 	v1 += d1;
168150bf276cStholo 	v2 += d1;
168250bf276cStholo 
168350bf276cStholo 	if (*v1 == '.')
168450bf276cStholo 	    ++v1;
168550bf276cStholo 	if (*v2 == '.')
168650bf276cStholo 	    ++v2;
168750bf276cStholo     }
168850bf276cStholo }
1689