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