/contrib/cvs/src/log.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 1818 lines · 1380 code · 204 blank · 234 comment · 393 complexity · 48a6770b46c982fbdcd876da090b4b01 MD5 · raw file

  1. /*
  2. * Copyright (C) 1986-2008 The Free Software Foundation, Inc.
  3. *
  4. * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
  5. * and others.
  6. *
  7. * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
  8. * Portions Copyright (C) 1989-1992, Brian Berliner
  9. *
  10. * You may distribute under the terms of the GNU General Public License as
  11. * specified in the README file that comes with the CVS source distribution.
  12. *
  13. * Print Log Information
  14. *
  15. * Prints the RCS "log" (rlog) information for the specified files. With no
  16. * argument, prints the log information for all the files in the directory
  17. * (recursive by default).
  18. *
  19. * $FreeBSD$
  20. */
  21. #include "cvs.h"
  22. #include <assert.h>
  23. /* This structure holds information parsed from the -r option. */
  24. struct option_revlist
  25. {
  26. /* The next -r option. */
  27. struct option_revlist *next;
  28. /* The first revision to print. This is NULL if the range is
  29. :rev, or if no revision is given. */
  30. char *first;
  31. /* The last revision to print. This is NULL if the range is rev:,
  32. or if no revision is given. If there is no colon, first and
  33. last are the same. */
  34. char *last;
  35. /* Nonzero if there was a trailing `.', which means to print only
  36. the head revision of a branch. */
  37. int branchhead;
  38. /* Nonzero if first and last are inclusive. */
  39. int inclusive;
  40. };
  41. /* This structure holds information derived from option_revlist given
  42. a particular RCS file. */
  43. struct revlist
  44. {
  45. /* The next pair. */
  46. struct revlist *next;
  47. /* The first numeric revision to print. */
  48. char *first;
  49. /* The last numeric revision to print. */
  50. char *last;
  51. /* The number of fields in these revisions (one more than
  52. numdots). */
  53. int fields;
  54. /* Whether first & last are to be included or excluded. */
  55. int inclusive;
  56. };
  57. /* This structure holds information parsed from the -d option. */
  58. struct datelist
  59. {
  60. /* The next date. */
  61. struct datelist *next;
  62. /* The starting date. */
  63. char *start;
  64. /* The ending date. */
  65. char *end;
  66. /* Nonzero if the range is inclusive rather than exclusive. */
  67. int inclusive;
  68. };
  69. /* This structure is used to pass information through start_recursion. */
  70. struct log_data
  71. {
  72. /* Nonzero if the -R option was given, meaning that only the name
  73. of the RCS file should be printed. */
  74. int nameonly;
  75. /* Nonzero if the -h option was given, meaning that only header
  76. information should be printed. */
  77. int header;
  78. /* Nonzero if the -t option was given, meaning that only the
  79. header and the descriptive text should be printed. */
  80. int long_header;
  81. /* Nonzero if the -N option was seen, meaning that tag information
  82. should not be printed. */
  83. int notags;
  84. /* Nonzero if the -b option was seen, meaning that revisions
  85. on the default branch should be printed. */
  86. int default_branch;
  87. /* Nonzero if the -S option was seen, meaning that the header/name
  88. should be suppressed if no revisions are selected. */
  89. int sup_header;
  90. /* If not NULL, the value given for the -r option, which lists
  91. sets of revisions to be printed. */
  92. struct option_revlist *revlist;
  93. /* If not NULL, the date pairs given for the -d option, which
  94. select date ranges to print. */
  95. struct datelist *datelist;
  96. /* If not NULL, the single dates given for the -d option, which
  97. select specific revisions to print based on a date. */
  98. struct datelist *singledatelist;
  99. /* If not NULL, the list of states given for the -s option, which
  100. only prints revisions of given states. */
  101. List *statelist;
  102. /* If not NULL, the list of login names given for the -w option,
  103. which only prints revisions checked in by given users. */
  104. List *authorlist;
  105. };
  106. /* This structure is used to pass information through walklist. */
  107. struct log_data_and_rcs
  108. {
  109. struct log_data *log_data;
  110. struct revlist *revlist;
  111. RCSNode *rcs;
  112. };
  113. static int rlog_proc PROTO((int argc, char **argv, char *xwhere,
  114. char *mwhere, char *mfile, int shorten,
  115. int local_specified, char *mname, char *msg));
  116. static Dtype log_dirproc PROTO ((void *callerdat, const char *dir,
  117. const char *repository,
  118. const char *update_dir,
  119. List *entries));
  120. static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo));
  121. static struct option_revlist *log_parse_revlist PROTO ((const char *));
  122. static void log_parse_date PROTO ((struct log_data *, const char *));
  123. static void log_parse_list PROTO ((List **, const char *));
  124. static struct revlist *log_expand_revlist PROTO ((RCSNode *, char *,
  125. struct option_revlist *,
  126. int));
  127. static void log_free_revlist PROTO ((struct revlist *));
  128. static int log_version_requested PROTO ((struct log_data *, struct revlist *,
  129. RCSNode *, RCSVers *));
  130. static int log_symbol PROTO ((Node *, void *));
  131. static int log_count PROTO ((Node *, void *));
  132. static int log_fix_singledate PROTO ((Node *, void *));
  133. static int log_count_print PROTO ((Node *, void *));
  134. static void log_tree PROTO ((struct log_data *, struct revlist *,
  135. RCSNode *, const char *));
  136. static void log_abranch PROTO ((struct log_data *, struct revlist *,
  137. RCSNode *, const char *));
  138. static void log_version PROTO ((struct log_data *, struct revlist *,
  139. RCSNode *, RCSVers *, int));
  140. static int log_branch PROTO ((Node *, void *));
  141. static int version_compare PROTO ((const char *, const char *, int));
  142. static struct log_data log_data;
  143. static int is_rlog;
  144. static const char *const log_usage[] =
  145. {
  146. "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n",
  147. " [-w[logins]] [files...]\n",
  148. "\t-l\tLocal directory only, no recursion.\n",
  149. "\t-b\tList revisions on the default branch.\n",
  150. "\t-h\tOnly print header.\n",
  151. "\t-R\tOnly print name of RCS file.\n",
  152. "\t-t\tOnly print header and descriptive text.\n",
  153. "\t-N\tDo not list tags.\n",
  154. "\t-n\tList tags (default).\n",
  155. "\t-S\tDo not print name/header if no revisions selected. -d, -r,\n",
  156. "\t\t-s, & -w have little effect in conjunction with -b, -h, -R, and\n",
  157. "\t\t-t without this option.\n",
  158. "\t-r[revisions]\tA comma-separated list of revisions to print:\n",
  159. "\t rev1:rev2 Between rev1 and rev2, including rev1 and rev2.\n",
  160. "\t rev1::rev2 Between rev1 and rev2, excluding rev1.\n",
  161. "\t rev: rev and following revisions on the same branch.\n",
  162. "\t rev:: After rev on the same branch.\n",
  163. "\t :rev rev and previous revisions on the same branch.\n",
  164. "\t ::rev rev and previous revisions on the same branch.\n",
  165. "\t rev Just rev.\n",
  166. "\t branch All revisions on the branch.\n",
  167. "\t branch. The last revision on the branch.\n",
  168. "\t-d dates\tA semicolon-separated list of dates\n",
  169. "\t \t(D1<D2 for range, D for latest before).\n",
  170. "\t-s states\tOnly list revisions with specified states.\n",
  171. "\t-w[logins]\tOnly list revisions checked in by specified logins.\n",
  172. "(Specify the --help global option for a list of other help options)\n",
  173. NULL
  174. };
  175. #ifdef CLIENT_SUPPORT
  176. /* Helper function for send_arg_list. */
  177. static int send_one PROTO ((Node *, void *));
  178. static int
  179. send_one (node, closure)
  180. Node *node;
  181. void *closure;
  182. {
  183. char *option = (char *) closure;
  184. send_to_server ("Argument ", 0);
  185. send_to_server (option, 0);
  186. if (strcmp (node->key, "@@MYSELF") == 0)
  187. /* It is a bare -w option. Note that we must send it as
  188. -w rather than messing with getcaller() or something (which on
  189. the client will return garbage). */
  190. ;
  191. else
  192. send_to_server (node->key, 0);
  193. send_to_server ("\012", 0);
  194. return 0;
  195. }
  196. /* For each element in ARG, send an argument consisting of OPTION
  197. concatenated with that element. */
  198. static void send_arg_list PROTO ((char *, List *));
  199. static void
  200. send_arg_list (option, arg)
  201. char *option;
  202. List *arg;
  203. {
  204. if (arg == NULL)
  205. return;
  206. walklist (arg, send_one, (void *)option);
  207. }
  208. #endif
  209. int
  210. cvslog (argc, argv)
  211. int argc;
  212. char **argv;
  213. {
  214. int c;
  215. int err = 0;
  216. int local = 0;
  217. struct option_revlist **prl;
  218. is_rlog = (strcmp (cvs_cmd_name, "rlog") == 0);
  219. if (argc == -1)
  220. usage (log_usage);
  221. memset (&log_data, 0, sizeof log_data);
  222. prl = &log_data.revlist;
  223. optind = 0;
  224. while ((c = getopt (argc, argv, "+bd:hlNnSRr::s:tw::")) != -1)
  225. {
  226. switch (c)
  227. {
  228. case 'b':
  229. log_data.default_branch = 1;
  230. break;
  231. case 'd':
  232. log_parse_date (&log_data, optarg);
  233. break;
  234. case 'h':
  235. log_data.header = 1;
  236. break;
  237. case 'l':
  238. local = 1;
  239. break;
  240. case 'N':
  241. log_data.notags = 1;
  242. break;
  243. case 'n':
  244. log_data.notags = 0;
  245. break;
  246. case 'S':
  247. log_data.sup_header = 1;
  248. break;
  249. case 'R':
  250. log_data.nameonly = 1;
  251. break;
  252. case 'r':
  253. *prl = log_parse_revlist (optarg);
  254. prl = &(*prl)->next;
  255. break;
  256. case 's':
  257. log_parse_list (&log_data.statelist, optarg);
  258. break;
  259. case 't':
  260. log_data.long_header = 1;
  261. break;
  262. case 'w':
  263. if (optarg != NULL)
  264. log_parse_list (&log_data.authorlist, optarg);
  265. else
  266. log_parse_list (&log_data.authorlist, "@@MYSELF");
  267. break;
  268. case '?':
  269. default:
  270. usage (log_usage);
  271. break;
  272. }
  273. }
  274. argc -= optind;
  275. argv += optind;
  276. wrap_setup ();
  277. #ifdef CLIENT_SUPPORT
  278. if (current_parsed_root->isremote)
  279. {
  280. struct datelist *p;
  281. struct option_revlist *rp;
  282. char datetmp[MAXDATELEN];
  283. /* We're the local client. Fire up the remote server. */
  284. start_server ();
  285. if (is_rlog && !supported_request ("rlog"))
  286. error (1, 0, "server does not support rlog");
  287. ign_setup ();
  288. if (log_data.default_branch)
  289. send_arg ("-b");
  290. while (log_data.datelist != NULL)
  291. {
  292. p = log_data.datelist;
  293. log_data.datelist = p->next;
  294. assert (p->start != NULL && p->end != NULL);
  295. send_to_server ("Argument -d\012", 0);
  296. send_to_server ("Argument ", 0);
  297. date_to_internet (datetmp, p->start);
  298. send_to_server (datetmp, 0);
  299. if (p->inclusive)
  300. send_to_server ("<=", 0);
  301. else
  302. send_to_server ("<", 0);
  303. date_to_internet (datetmp, p->end);
  304. send_to_server (datetmp, 0);
  305. send_to_server ("\012", 0);
  306. free (p->start);
  307. free (p->end);
  308. free (p);
  309. }
  310. while (log_data.singledatelist != NULL)
  311. {
  312. p = log_data.singledatelist;
  313. log_data.singledatelist = p->next;
  314. assert (p->end != NULL);
  315. send_to_server ("Argument -d\012", 0);
  316. send_to_server ("Argument ", 0);
  317. date_to_internet (datetmp, p->end);
  318. send_to_server (datetmp, 0);
  319. send_to_server ("\012", 0);
  320. free (p->end);
  321. free (p);
  322. }
  323. if (log_data.header)
  324. send_arg ("-h");
  325. if (local)
  326. send_arg("-l");
  327. if (log_data.notags)
  328. send_arg("-N");
  329. if (log_data.sup_header)
  330. send_arg("-S");
  331. if (log_data.nameonly)
  332. send_arg("-R");
  333. if (log_data.long_header)
  334. send_arg("-t");
  335. while (log_data.revlist != NULL)
  336. {
  337. rp = log_data.revlist;
  338. log_data.revlist = rp->next;
  339. send_to_server ("Argument -r", 0);
  340. if (rp->branchhead)
  341. {
  342. if (rp->first != NULL)
  343. send_to_server (rp->first, 0);
  344. send_to_server (".", 1);
  345. }
  346. else
  347. {
  348. if (rp->first != NULL)
  349. send_to_server (rp->first, 0);
  350. send_to_server (":", 1);
  351. if (!rp->inclusive)
  352. send_to_server (":", 1);
  353. if (rp->last != NULL)
  354. send_to_server (rp->last, 0);
  355. }
  356. send_to_server ("\012", 0);
  357. if (rp->first)
  358. free (rp->first);
  359. if (rp->last)
  360. free (rp->last);
  361. free (rp);
  362. }
  363. send_arg_list ("-s", log_data.statelist);
  364. dellist (&log_data.statelist);
  365. send_arg_list ("-w", log_data.authorlist);
  366. dellist (&log_data.authorlist);
  367. send_arg ("--");
  368. if (is_rlog)
  369. {
  370. int i;
  371. for (i = 0; i < argc; i++)
  372. send_arg (argv[i]);
  373. send_to_server ("rlog\012", 0);
  374. }
  375. else
  376. {
  377. send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
  378. send_file_names (argc, argv, SEND_EXPAND_WILD);
  379. send_to_server ("log\012", 0);
  380. }
  381. err = get_responses_and_close ();
  382. return err;
  383. }
  384. #endif
  385. /* OK, now that we know we are local/server, we can resolve @@MYSELF
  386. into our user name. */
  387. if (findnode (log_data.authorlist, "@@MYSELF") != NULL)
  388. log_parse_list (&log_data.authorlist, getcaller ());
  389. if (is_rlog)
  390. {
  391. DBM *db;
  392. int i;
  393. db = open_module ();
  394. for (i = 0; i < argc; i++)
  395. {
  396. err += do_module (db, argv[i], MISC, "Logging", rlog_proc,
  397. (char *) NULL, 0, local, 0, 0, (char *) NULL);
  398. }
  399. close_module (db);
  400. }
  401. else
  402. {
  403. err = rlog_proc (argc + 1, argv - 1, (char *) NULL,
  404. (char *) NULL, (char *) NULL, 0, local, (char *) NULL,
  405. (char *) NULL);
  406. }
  407. while (log_data.revlist)
  408. {
  409. struct option_revlist *rl = log_data.revlist->next;
  410. if (log_data.revlist->first)
  411. free (log_data.revlist->first);
  412. if (log_data.revlist->last)
  413. free (log_data.revlist->last);
  414. free (log_data.revlist);
  415. log_data.revlist = rl;
  416. }
  417. while (log_data.datelist)
  418. {
  419. struct datelist *nd = log_data.datelist->next;
  420. if (log_data.datelist->start)
  421. free (log_data.datelist->start);
  422. if (log_data.datelist->end)
  423. free (log_data.datelist->end);
  424. free (log_data.datelist);
  425. log_data.datelist = nd;
  426. }
  427. while (log_data.singledatelist)
  428. {
  429. struct datelist *nd = log_data.singledatelist->next;
  430. if (log_data.singledatelist->start)
  431. free (log_data.singledatelist->start);
  432. if (log_data.singledatelist->end)
  433. free (log_data.singledatelist->end);
  434. free (log_data.singledatelist);
  435. log_data.singledatelist = nd;
  436. }
  437. dellist (&log_data.statelist);
  438. dellist (&log_data.authorlist);
  439. return (err);
  440. }
  441. static int
  442. rlog_proc (argc, argv, xwhere, mwhere, mfile, shorten, local, mname, msg)
  443. int argc;
  444. char **argv;
  445. char *xwhere;
  446. char *mwhere;
  447. char *mfile;
  448. int shorten;
  449. int local;
  450. char *mname;
  451. char *msg;
  452. {
  453. /* Begin section which is identical to patch_proc--should this
  454. be abstracted out somehow? */
  455. char *myargv[2];
  456. int err = 0;
  457. int which;
  458. char *repository;
  459. char *where;
  460. if (is_rlog)
  461. {
  462. repository = xmalloc (strlen (current_parsed_root->directory)
  463. + strlen (argv[0])
  464. + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
  465. (void)sprintf (repository, "%s/%s",
  466. current_parsed_root->directory, argv[0]);
  467. where = xmalloc (strlen (argv[0])
  468. + (mfile == NULL ? 0 : strlen (mfile) + 1)
  469. + 1);
  470. (void) strcpy (where, argv[0]);
  471. /* If mfile isn't null, we need to set up to do only part of theu
  472. * module.
  473. */
  474. if (mfile != NULL)
  475. {
  476. char *cp;
  477. char *path;
  478. /* If the portion of the module is a path, put the dir part on
  479. * repos.
  480. */
  481. if ((cp = strrchr (mfile, '/')) != NULL)
  482. {
  483. *cp = '\0';
  484. (void)strcat (repository, "/");
  485. (void)strcat (repository, mfile);
  486. (void)strcat (where, "/");
  487. (void)strcat (where, mfile);
  488. mfile = cp + 1;
  489. }
  490. /* take care of the rest */
  491. path = xmalloc (strlen (repository) + strlen (mfile) + 5);
  492. (void)sprintf (path, "%s/%s", repository, mfile);
  493. if (isdir (path))
  494. {
  495. /* directory means repository gets the dir tacked on */
  496. (void)strcpy (repository, path);
  497. (void)strcat (where, "/");
  498. (void)strcat (where, mfile);
  499. }
  500. else
  501. {
  502. myargv[0] = argv[0];
  503. myargv[1] = mfile;
  504. argc = 2;
  505. argv = myargv;
  506. }
  507. free (path);
  508. }
  509. /* cd to the starting repository */
  510. if (CVS_CHDIR (repository) < 0)
  511. {
  512. error (0, errno, "cannot chdir to %s", repository);
  513. free (repository);
  514. free (where);
  515. return 1;
  516. }
  517. /* End section which is identical to patch_proc. */
  518. which = W_REPOS | W_ATTIC;
  519. }
  520. else
  521. {
  522. repository = NULL;
  523. where = NULL;
  524. which = W_LOCAL | W_REPOS | W_ATTIC;
  525. }
  526. err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
  527. (DIRLEAVEPROC) NULL, (void *) &log_data,
  528. argc - 1, argv + 1, local, which, 0, CVS_LOCK_READ,
  529. where, 1, repository);
  530. if (!(which & W_LOCAL)) free (repository);
  531. if (where) free (where);
  532. return err;
  533. }
  534. /*
  535. * Parse a revision list specification.
  536. */
  537. static struct option_revlist *
  538. log_parse_revlist (argstring)
  539. const char *argstring;
  540. {
  541. char *orig_copy, *copy;
  542. struct option_revlist *ret, **pr;
  543. /* Unfortunately, rlog accepts -r without an argument to mean that
  544. latest revision on the default branch, so we must support that
  545. for compatibility. */
  546. if (argstring == NULL)
  547. argstring = "";
  548. ret = NULL;
  549. pr = &ret;
  550. /* Copy the argument into memory so that we can change it. We
  551. don't want to change the argument because, at least as of this
  552. writing, we will use it if we send the arguments to the server. */
  553. orig_copy = copy = xstrdup (argstring);
  554. while (copy != NULL)
  555. {
  556. char *comma;
  557. struct option_revlist *r;
  558. comma = strchr (copy, ',');
  559. if (comma != NULL)
  560. *comma++ = '\0';
  561. r = (struct option_revlist *) xmalloc (sizeof *r);
  562. r->next = NULL;
  563. r->first = copy;
  564. r->branchhead = 0;
  565. r->last = strchr (copy, ':');
  566. if (r->last != NULL)
  567. {
  568. *r->last++ = '\0';
  569. r->inclusive = (*r->last != ':');
  570. if (!r->inclusive)
  571. r->last++;
  572. }
  573. else
  574. {
  575. r->last = r->first;
  576. r->inclusive = 1;
  577. if (r->first[0] != '\0' && r->first[strlen (r->first) - 1] == '.')
  578. {
  579. r->branchhead = 1;
  580. r->first[strlen (r->first) - 1] = '\0';
  581. }
  582. }
  583. if (*r->first == '\0')
  584. r->first = NULL;
  585. if (*r->last == '\0')
  586. r->last = NULL;
  587. if (r->first != NULL)
  588. r->first = xstrdup (r->first);
  589. if (r->last != NULL)
  590. r->last = xstrdup (r->last);
  591. *pr = r;
  592. pr = &r->next;
  593. copy = comma;
  594. }
  595. free (orig_copy);
  596. return ret;
  597. }
  598. /*
  599. * Parse a date specification.
  600. */
  601. static void
  602. log_parse_date (log_data, argstring)
  603. struct log_data *log_data;
  604. const char *argstring;
  605. {
  606. char *orig_copy, *copy;
  607. /* Copy the argument into memory so that we can change it. We
  608. don't want to change the argument because, at least as of this
  609. writing, we will use it if we send the arguments to the server. */
  610. orig_copy = copy = xstrdup (argstring);
  611. while (copy != NULL)
  612. {
  613. struct datelist *nd, **pd;
  614. char *cpend, *cp, *ds, *de;
  615. nd = (struct datelist *) xmalloc (sizeof *nd);
  616. cpend = strchr (copy, ';');
  617. if (cpend != NULL)
  618. *cpend++ = '\0';
  619. pd = &log_data->datelist;
  620. nd->inclusive = 0;
  621. if ((cp = strchr (copy, '>')) != NULL)
  622. {
  623. *cp++ = '\0';
  624. if (*cp == '=')
  625. {
  626. ++cp;
  627. nd->inclusive = 1;
  628. }
  629. ds = cp;
  630. de = copy;
  631. }
  632. else if ((cp = strchr (copy, '<')) != NULL)
  633. {
  634. *cp++ = '\0';
  635. if (*cp == '=')
  636. {
  637. ++cp;
  638. nd->inclusive = 1;
  639. }
  640. ds = copy;
  641. de = cp;
  642. }
  643. else
  644. {
  645. ds = NULL;
  646. de = copy;
  647. pd = &log_data->singledatelist;
  648. }
  649. if (ds == NULL)
  650. nd->start = NULL;
  651. else if (*ds != '\0')
  652. nd->start = Make_Date (ds);
  653. else
  654. {
  655. /* 1970 was the beginning of time, as far as get_date and
  656. Make_Date are concerned. FIXME: That is true only if time_t
  657. is a POSIX-style time and there is nothing in ANSI that
  658. mandates that. It would be cleaner to set a flag saying
  659. whether or not there is a start date. */
  660. nd->start = Make_Date ("1/1/1970 UTC");
  661. }
  662. if (*de != '\0')
  663. nd->end = Make_Date (de);
  664. else
  665. {
  666. /* We want to set the end date to some time sufficiently far
  667. in the future to pick up all revisions that have been
  668. created since the specified date and the time `cvs log'
  669. completes. FIXME: The date in question only makes sense
  670. if time_t is a POSIX-style time and it is 32 bits
  671. and signed. We should instead be setting a flag saying
  672. whether or not there is an end date. Note that using
  673. something like "next week" would break the testsuite (and,
  674. perhaps less importantly, loses if the clock is set grossly
  675. wrong). */
  676. nd->end = Make_Date ("2038-01-01");
  677. }
  678. nd->next = *pd;
  679. *pd = nd;
  680. copy = cpend;
  681. }
  682. free (orig_copy);
  683. }
  684. /*
  685. * Parse a comma separated list of items, and add each one to *PLIST.
  686. */
  687. static void
  688. log_parse_list (plist, argstring)
  689. List **plist;
  690. const char *argstring;
  691. {
  692. while (1)
  693. {
  694. Node *p;
  695. char *cp;
  696. p = getnode ();
  697. cp = strchr (argstring, ',');
  698. if (cp == NULL)
  699. p->key = xstrdup (argstring);
  700. else
  701. {
  702. size_t len;
  703. len = cp - argstring;
  704. p->key = xmalloc (len + 1);
  705. strncpy (p->key, argstring, len);
  706. p->key[len] = '\0';
  707. }
  708. if (*plist == NULL)
  709. *plist = getlist ();
  710. if (addnode (*plist, p) != 0)
  711. freenode (p);
  712. if (cp == NULL)
  713. break;
  714. argstring = cp + 1;
  715. }
  716. }
  717. static int printlock_proc PROTO ((Node *, void *));
  718. static int
  719. printlock_proc (lock, foo)
  720. Node *lock;
  721. void *foo;
  722. {
  723. cvs_output ("\n\t", 2);
  724. cvs_output (lock->data, 0);
  725. cvs_output (": ", 2);
  726. cvs_output (lock->key, 0);
  727. return 0;
  728. }
  729. /*
  730. * Do an rlog on a file
  731. */
  732. static int
  733. log_fileproc (callerdat, finfo)
  734. void *callerdat;
  735. struct file_info *finfo;
  736. {
  737. struct log_data *log_data = (struct log_data *) callerdat;
  738. Node *p;
  739. char *baserev;
  740. int selrev = -1;
  741. RCSNode *rcsfile;
  742. char buf[50];
  743. struct revlist *revlist = NULL;
  744. struct log_data_and_rcs log_data_and_rcs;
  745. rcsfile = finfo->rcs;
  746. p = findnode (finfo->entries, finfo->file);
  747. if (p != NULL)
  748. {
  749. Entnode *e = p->data;
  750. baserev = e->version;
  751. if (baserev[0] == '-') ++baserev;
  752. }
  753. else
  754. baserev = NULL;
  755. if (rcsfile == NULL)
  756. {
  757. /* no rcs file. What *do* we know about this file? */
  758. if (baserev != NULL)
  759. {
  760. if (baserev[0] == '0' && baserev[1] == '\0')
  761. {
  762. if (!really_quiet)
  763. error (0, 0, "%s has been added, but not committed",
  764. finfo->file);
  765. return 0;
  766. }
  767. }
  768. if (!really_quiet)
  769. error (0, 0, "nothing known about %s", finfo->file);
  770. return 1;
  771. }
  772. if (log_data->sup_header || !log_data->nameonly)
  773. {
  774. /* We will need all the information in the RCS file. */
  775. RCS_fully_parse (rcsfile);
  776. /* Turn any symbolic revisions in the revision list into numeric
  777. revisions. */
  778. revlist = log_expand_revlist (rcsfile, baserev, log_data->revlist,
  779. log_data->default_branch);
  780. if (log_data->sup_header
  781. || (!log_data->header && !log_data->long_header))
  782. {
  783. log_data_and_rcs.log_data = log_data;
  784. log_data_and_rcs.revlist = revlist;
  785. log_data_and_rcs.rcs = rcsfile;
  786. /* If any single dates were specified, we need to identify the
  787. revisions they select. Each one selects the single
  788. revision, which is otherwise selected, of that date or
  789. earlier. The log_fix_singledate routine will fill in the
  790. start date for each specific revision. */
  791. if (log_data->singledatelist != NULL)
  792. walklist (rcsfile->versions, log_fix_singledate,
  793. (void *)&log_data_and_rcs);
  794. selrev = walklist (rcsfile->versions, log_count_print,
  795. (void *)&log_data_and_rcs);
  796. if (log_data->sup_header && selrev == 0)
  797. {
  798. log_free_revlist (revlist);
  799. return 0;
  800. }
  801. }
  802. }
  803. if (log_data->nameonly)
  804. {
  805. cvs_output (rcsfile->path, 0);
  806. cvs_output ("\n", 1);
  807. log_free_revlist (revlist);
  808. return 0;
  809. }
  810. /* The output here is intended to be exactly compatible with the
  811. output of rlog. I'm not sure whether this code should be here
  812. or in rcs.c; I put it here because it is specific to the log
  813. function, even though it uses information gathered by the
  814. functions in rcs.c. */
  815. cvs_output ("\n", 1);
  816. cvs_output ("RCS file: ", 0);
  817. cvs_output (rcsfile->path, 0);
  818. if (!is_rlog)
  819. {
  820. cvs_output ("\nWorking file: ", 0);
  821. if (finfo->update_dir[0] != '\0')
  822. {
  823. cvs_output (finfo->update_dir, 0);
  824. cvs_output ("/", 0);
  825. }
  826. cvs_output (finfo->file, 0);
  827. }
  828. cvs_output ("\nhead:", 0);
  829. if (rcsfile->head != NULL)
  830. {
  831. cvs_output (" ", 1);
  832. cvs_output (rcsfile->head, 0);
  833. }
  834. cvs_output ("\nbranch:", 0);
  835. if (rcsfile->branch != NULL)
  836. {
  837. cvs_output (" ", 1);
  838. cvs_output (rcsfile->branch, 0);
  839. }
  840. cvs_output ("\nlocks:", 0);
  841. if (rcsfile->strict_locks)
  842. cvs_output (" strict", 0);
  843. walklist (RCS_getlocks (rcsfile), printlock_proc, NULL);
  844. cvs_output ("\naccess list:", 0);
  845. if (rcsfile->access != NULL)
  846. {
  847. const char *cp;
  848. cp = rcsfile->access;
  849. while (*cp != '\0')
  850. {
  851. const char *cp2;
  852. cvs_output ("\n\t", 2);
  853. cp2 = cp;
  854. while (!isspace ((unsigned char) *cp2) && *cp2 != '\0')
  855. ++cp2;
  856. cvs_output (cp, cp2 - cp);
  857. cp = cp2;
  858. while (isspace ((unsigned char) *cp) && *cp != '\0')
  859. ++cp;
  860. }
  861. }
  862. if (!log_data->notags)
  863. {
  864. List *syms;
  865. cvs_output ("\nsymbolic names:", 0);
  866. syms = RCS_symbols (rcsfile);
  867. walklist (syms, log_symbol, NULL);
  868. }
  869. cvs_output ("\nkeyword substitution: ", 0);
  870. if (rcsfile->expand == NULL)
  871. cvs_output ("kv", 2);
  872. else
  873. cvs_output (rcsfile->expand, 0);
  874. cvs_output ("\ntotal revisions: ", 0);
  875. sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL));
  876. cvs_output (buf, 0);
  877. if (selrev >= 0)
  878. {
  879. cvs_output (";\tselected revisions: ", 0);
  880. sprintf (buf, "%d", selrev);
  881. cvs_output (buf, 0);
  882. }
  883. cvs_output ("\n", 1);
  884. if (!log_data->header || log_data->long_header)
  885. {
  886. cvs_output ("description:\n", 0);
  887. if (rcsfile->desc != NULL)
  888. cvs_output (rcsfile->desc, 0);
  889. }
  890. if (!log_data->header && ! log_data->long_header && rcsfile->head != NULL)
  891. {
  892. p = findnode (rcsfile->versions, rcsfile->head);
  893. if (p == NULL)
  894. error (1, 0, "can not find head revision in `%s'",
  895. finfo->fullname);
  896. while (p != NULL)
  897. {
  898. RCSVers *vers = p->data;
  899. log_version (log_data, revlist, rcsfile, vers, 1);
  900. if (vers->next == NULL)
  901. p = NULL;
  902. else
  903. {
  904. p = findnode (rcsfile->versions, vers->next);
  905. if (p == NULL)
  906. error (1, 0, "can not find next revision `%s' in `%s'",
  907. vers->next, finfo->fullname);
  908. }
  909. }
  910. log_tree (log_data, revlist, rcsfile, rcsfile->head);
  911. }
  912. cvs_output("\
  913. =============================================================================\n",
  914. 0);
  915. /* Free up the new revlist and restore the old one. */
  916. log_free_revlist (revlist);
  917. /* If singledatelist is not NULL, free up the start dates we added
  918. to it. */
  919. if (log_data->singledatelist != NULL)
  920. {
  921. struct datelist *d;
  922. for (d = log_data->singledatelist; d != NULL; d = d->next)
  923. {
  924. if (d->start != NULL)
  925. free (d->start);
  926. d->start = NULL;
  927. }
  928. }
  929. return 0;
  930. }
  931. /*
  932. * Fix up a revision list in order to compare it against versions.
  933. * Expand any symbolic revisions.
  934. */
  935. static struct revlist *
  936. log_expand_revlist (rcs, baserev, revlist, default_branch)
  937. RCSNode *rcs;
  938. char *baserev;
  939. struct option_revlist *revlist;
  940. int default_branch;
  941. {
  942. struct option_revlist *r;
  943. struct revlist *ret, **pr;
  944. ret = NULL;
  945. pr = &ret;
  946. for (r = revlist; r != NULL; r = r->next)
  947. {
  948. struct revlist *nr;
  949. nr = (struct revlist *) xmalloc (sizeof *nr);
  950. nr->inclusive = r->inclusive;
  951. if (r->first == NULL && r->last == NULL)
  952. {
  953. /* If both first and last are NULL, it means that we want
  954. just the head of the default branch, which is RCS_head. */
  955. nr->first = RCS_head (rcs);
  956. if (!nr->first)
  957. {
  958. if (!really_quiet)
  959. error (0, 0, "No head revision in archive `%s'.",
  960. rcs->path);
  961. nr->last = NULL;
  962. nr->fields = 0;
  963. }
  964. else
  965. {
  966. nr->last = xstrdup (nr->first);
  967. nr->fields = numdots (nr->first) + 1;
  968. }
  969. }
  970. else if (r->branchhead)
  971. {
  972. char *branch;
  973. assert (r->first != NULL);
  974. /* Print just the head of the branch. */
  975. if (isdigit ((unsigned char) r->first[0]))
  976. nr->first = RCS_getbranch (rcs, r->first, 1);
  977. else
  978. {
  979. branch = RCS_whatbranch (rcs, r->first);
  980. if (branch == NULL)
  981. nr->first = NULL;
  982. else
  983. {
  984. nr->first = RCS_getbranch (rcs, branch, 1);
  985. free (branch);
  986. }
  987. }
  988. if (!nr->first)
  989. {
  990. if (!really_quiet)
  991. error (0, 0, "warning: no branch `%s' in `%s'",
  992. r->first, rcs->path);
  993. nr->last = NULL;
  994. nr->fields = 0;
  995. }
  996. else
  997. {
  998. nr->last = xstrdup (nr->first);
  999. nr->fields = numdots (nr->first) + 1;
  1000. }
  1001. }
  1002. else
  1003. {
  1004. if (r->first == NULL || isdigit ((unsigned char) r->first[0]))
  1005. nr->first = xstrdup (r->first);
  1006. else
  1007. {
  1008. if (baserev && strcmp (r->first, TAG_BASE) == 0)
  1009. nr->first = xstrdup (baserev);
  1010. else if (RCS_nodeisbranch (rcs, r->first))
  1011. nr->first = RCS_whatbranch (rcs, r->first);
  1012. else
  1013. nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL);
  1014. if (nr->first == NULL && !really_quiet)
  1015. {
  1016. error (0, 0, "warning: no revision `%s' in `%s'",
  1017. r->first, rcs->path);
  1018. }
  1019. }
  1020. if (r->last == r->first || (r->last != NULL && r->first != NULL &&
  1021. strcmp (r->last, r->first) == 0))
  1022. nr->last = xstrdup (nr->first);
  1023. else if (r->last == NULL || isdigit ((unsigned char) r->last[0]))
  1024. nr->last = xstrdup (r->last);
  1025. else
  1026. {
  1027. if (baserev && strcmp (r->last, TAG_BASE) == 0)
  1028. nr->last = xstrdup (baserev);
  1029. else if (RCS_nodeisbranch (rcs, r->last))
  1030. nr->last = RCS_whatbranch (rcs, r->last);
  1031. else
  1032. nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL);
  1033. if (nr->last == NULL && !really_quiet)
  1034. {
  1035. error (0, 0, "warning: no revision `%s' in `%s'",
  1036. r->last, rcs->path);
  1037. }
  1038. }
  1039. /* Process the revision numbers the same way that rlog
  1040. does. This code is a bit cryptic for my tastes, but
  1041. keeping the same implementation as rlog ensures a
  1042. certain degree of compatibility. */
  1043. if (r->first == NULL && nr->last != NULL)
  1044. {
  1045. nr->fields = numdots (nr->last) + 1;
  1046. if (nr->fields < 2)
  1047. nr->first = xstrdup (".0");
  1048. else
  1049. {
  1050. char *cp;
  1051. nr->first = xstrdup (nr->last);
  1052. cp = strrchr (nr->first, '.');
  1053. assert (cp);
  1054. strcpy (cp + 1, "0");
  1055. }
  1056. }
  1057. else if (r->last == NULL && nr->first != NULL)
  1058. {
  1059. nr->fields = numdots (nr->first) + 1;
  1060. nr->last = xstrdup (nr->first);
  1061. if (nr->fields < 2)
  1062. nr->last[0] = '\0';
  1063. else
  1064. {
  1065. char *cp;
  1066. cp = strrchr (nr->last, '.');
  1067. assert (cp);
  1068. *cp = '\0';
  1069. }
  1070. }
  1071. else if (nr->first == NULL || nr->last == NULL)
  1072. nr->fields = 0;
  1073. else if (strcmp (nr->first, nr->last) == 0)
  1074. nr->fields = numdots (nr->last) + 1;
  1075. else
  1076. {
  1077. int ord;
  1078. int dots1 = numdots (nr->first);
  1079. int dots2 = numdots (nr->last);
  1080. if (dots1 > dots2 || (dots1 == dots2 &&
  1081. version_compare (nr->first, nr->last, dots1 + 1) > 0))
  1082. {
  1083. char *tmp = nr->first;
  1084. nr->first = nr->last;
  1085. nr->last = tmp;
  1086. nr->fields = dots2 + 1;
  1087. dots2 = dots1;
  1088. dots1 = nr->fields - 1;
  1089. }
  1090. else
  1091. nr->fields = dots1 + 1;
  1092. dots1 += (nr->fields & 1);
  1093. ord = version_compare (nr->first, nr->last, dots1);
  1094. if (ord > 0 || (nr->fields > 2 && ord < 0))
  1095. {
  1096. error (0, 0,
  1097. "invalid branch or revision pair %s:%s in `%s'",
  1098. r->first, r->last, rcs->path);
  1099. free (nr->first);
  1100. nr->first = NULL;
  1101. free (nr->last);
  1102. nr->last = NULL;
  1103. nr->fields = 0;
  1104. }
  1105. else
  1106. {
  1107. if (nr->fields <= dots2 && (nr->fields & 1))
  1108. {
  1109. char *p = xmalloc (strlen (nr->first) + 3);
  1110. strcpy (p, nr->first);
  1111. strcat (p, ".0");
  1112. free (nr->first);
  1113. nr->first = p;
  1114. ++nr->fields;
  1115. }
  1116. while (nr->fields <= dots2)
  1117. {
  1118. char *p;
  1119. int i;
  1120. nr->next = NULL;
  1121. *pr = nr;
  1122. nr = (struct revlist *) xmalloc (sizeof *nr);
  1123. nr->inclusive = 1;
  1124. nr->first = xstrdup ((*pr)->last);
  1125. nr->last = xstrdup ((*pr)->last);
  1126. nr->fields = (*pr)->fields;
  1127. p = (*pr)->last;
  1128. for (i = 0; i < nr->fields; i++)
  1129. p = strchr (p, '.') + 1;
  1130. p[-1] = '\0';
  1131. p = strchr (nr->first + (p - (*pr)->last), '.');
  1132. if (p != NULL)
  1133. {
  1134. *++p = '0';
  1135. *++p = '\0';
  1136. nr->fields += 2;
  1137. }
  1138. else
  1139. ++nr->fields;
  1140. pr = &(*pr)->next;
  1141. }
  1142. }
  1143. }
  1144. }
  1145. nr->next = NULL;
  1146. *pr = nr;
  1147. pr = &nr->next;
  1148. }
  1149. /* If the default branch was requested, add a revlist entry for
  1150. it. This is how rlog handles this option. */
  1151. if (default_branch
  1152. && (rcs->head != NULL || rcs->branch != NULL))
  1153. {
  1154. struct revlist *nr;
  1155. nr = (struct revlist *) xmalloc (sizeof *nr);
  1156. if (rcs->branch != NULL)
  1157. nr->first = xstrdup (rcs->branch);
  1158. else
  1159. {
  1160. char *cp;
  1161. nr->first = xstrdup (rcs->head);
  1162. assert (nr->first);
  1163. cp = strrchr (nr->first, '.');
  1164. assert (cp);
  1165. *cp = '\0';
  1166. }
  1167. nr->last = xstrdup (nr->first);
  1168. nr->fields = numdots (nr->first) + 1;
  1169. nr->inclusive = 1;
  1170. nr->next = NULL;
  1171. *pr = nr;
  1172. }
  1173. return ret;
  1174. }
  1175. /*
  1176. * Free a revlist created by log_expand_revlist.
  1177. */
  1178. static void
  1179. log_free_revlist (revlist)
  1180. struct revlist *revlist;
  1181. {
  1182. struct revlist *r;
  1183. r = revlist;
  1184. while (r != NULL)
  1185. {
  1186. struct revlist *next;
  1187. if (r->first != NULL)
  1188. free (r->first);
  1189. if (r->last != NULL)
  1190. free (r->last);
  1191. next = r->next;
  1192. free (r);
  1193. r = next;
  1194. }
  1195. }
  1196. /*
  1197. * Return nonzero if a revision should be printed, based on the
  1198. * options provided.
  1199. */
  1200. static int
  1201. log_version_requested (log_data, revlist, rcs, vnode)
  1202. struct log_data *log_data;
  1203. struct revlist *revlist;
  1204. RCSNode *rcs;
  1205. RCSVers *vnode;
  1206. {
  1207. /* Handle the list of states from the -s option. */
  1208. if (log_data->statelist != NULL
  1209. && findnode (log_data->statelist, vnode->state) == NULL)
  1210. {
  1211. return 0;
  1212. }
  1213. /* Handle the list of authors from the -w option. */
  1214. if (log_data->authorlist != NULL)
  1215. {
  1216. if (vnode->author != NULL
  1217. && findnode (log_data->authorlist, vnode->author) == NULL)
  1218. {
  1219. return 0;
  1220. }
  1221. }
  1222. /* rlog considers all the -d options together when it decides
  1223. whether to print a revision, so we must be compatible. */
  1224. if (log_data->datelist != NULL || log_data->singledatelist != NULL)
  1225. {
  1226. struct datelist *d;
  1227. for (d = log_data->datelist; d != NULL; d = d->next)
  1228. {
  1229. int cmp;
  1230. cmp = RCS_datecmp (vnode->date, d->start);
  1231. if (cmp > 0 || (cmp == 0 && d->inclusive))
  1232. {
  1233. cmp = RCS_datecmp (vnode->date, d->end);
  1234. if (cmp < 0 || (cmp == 0 && d->inclusive))
  1235. break;
  1236. }
  1237. }
  1238. if (d == NULL)
  1239. {
  1240. /* Look through the list of specific dates. We want to
  1241. select the revision with the exact date found in the
  1242. start field. The commit code ensures that it is
  1243. impossible to check in multiple revisions of a single
  1244. file in a single second, so checking the date this way
  1245. should never select more than one revision. */
  1246. for (d = log_data->singledatelist; d != NULL; d = d->next)
  1247. {
  1248. if (d->start != NULL
  1249. && RCS_datecmp (vnode->date, d->start) == 0)
  1250. {
  1251. break;
  1252. }
  1253. }
  1254. if (d == NULL)
  1255. return 0;
  1256. }
  1257. }
  1258. /* If the -r or -b options were used, REVLIST will be non NULL,
  1259. and we print the union of the specified revisions. */
  1260. if (revlist != NULL)
  1261. {
  1262. char *v;
  1263. int vfields;
  1264. struct revlist *r;
  1265. /* This code is taken from rlog. */
  1266. v = vnode->version;
  1267. vfields = numdots (v) + 1;
  1268. for (r = revlist; r != NULL; r = r->next)
  1269. {
  1270. if (vfields == r->fields + (r->fields & 1) &&
  1271. (r->inclusive ? version_compare (v, r->first, r->fields) >= 0 :
  1272. version_compare (v, r->first, r->fields) > 0)
  1273. && version_compare (v, r->last, r->fields) <= 0)
  1274. {
  1275. return 1;
  1276. }
  1277. }
  1278. /* If we get here, then the -b and/or the -r option was used,
  1279. but did not match this revision, so we reject it. */
  1280. return 0;
  1281. }
  1282. /* By default, we print all revisions. */
  1283. return 1;
  1284. }
  1285. /*
  1286. * Output a single symbol. This is called via walklist.
  1287. */
  1288. /*ARGSUSED*/
  1289. static int
  1290. log_symbol (p, closure)
  1291. Node *p;
  1292. void *closure;
  1293. {
  1294. cvs_output ("\n\t", 2);
  1295. cvs_output (p->key, 0);
  1296. cvs_output (": ", 2);
  1297. cvs_output (p->data, 0);
  1298. return 0;
  1299. }
  1300. /*
  1301. * Count the number of entries on a list. This is called via walklist.
  1302. */
  1303. /*ARGSUSED*/
  1304. static int
  1305. log_count (p, closure)
  1306. Node *p;
  1307. void *closure;
  1308. {
  1309. return 1;
  1310. }
  1311. /*
  1312. * Sort out a single date specification by narrowing down the date
  1313. * until we find the specific selected revision.
  1314. */
  1315. static int
  1316. log_fix_singledate (p, closure)
  1317. Node *p;
  1318. void *closure;
  1319. {
  1320. struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
  1321. Node *pv;
  1322. RCSVers *vnode;
  1323. struct datelist *holdsingle, *holddate;
  1324. int requested;
  1325. pv = findnode (data->rcs->versions, p->key);
  1326. if (pv == NULL)
  1327. error (1, 0, "missing version `%s' in RCS file `%s'",
  1328. p->key, data->rcs->path);
  1329. vnode = pv->data;
  1330. /* We are only interested if this revision passes any other tests.
  1331. Temporarily clear log_data->singledatelist to avoid confusing
  1332. log_version_requested. We also clear log_data->datelist,
  1333. because rlog considers all the -d options together. We don't
  1334. want to reject a revision because it does not match a date pair
  1335. if we are going to select it on the basis of the singledate. */
  1336. holdsingle = data->log_data->singledatelist;
  1337. data->log_data->singledatelist = NULL;
  1338. holddate = data->log_data->datelist;
  1339. data->log_data->datelist = NULL;
  1340. requested = log_version_requested (data->log_data, data->revlist,
  1341. data->rcs, vnode);
  1342. data->log_data->singledatelist = holdsingle;
  1343. data->log_data->datelist = holddate;
  1344. if (requested)
  1345. {
  1346. struct datelist *d;
  1347. /* For each single date, if this revision is before the
  1348. specified date, but is closer than the previously selected
  1349. revision, select it instead. */
  1350. for (d = data->log_data->singledatelist; d != NULL; d = d->next)
  1351. {
  1352. if (RCS_datecmp (vnode->date, d->end) <= 0
  1353. && (d->start == NULL
  1354. || RCS_datecmp (vnode->date, d->start) > 0))
  1355. {
  1356. if (d->start != NULL)
  1357. free (d->start);
  1358. d->start = xstrdup (vnode->date);
  1359. }
  1360. }
  1361. }
  1362. return 0;
  1363. }
  1364. /*
  1365. * Count the number of revisions we are going to print.
  1366. */
  1367. static int
  1368. log_count_print (p, closure)
  1369. Node *p;
  1370. void *closure;
  1371. {
  1372. struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure;
  1373. Node *pv;
  1374. pv = findnode (data->rcs->versions, p->key);
  1375. if (pv == NULL)
  1376. error (1, 0, "missing version `%s' in RCS file `%s'",
  1377. p->key, data->rcs->path);
  1378. if (log_version_requested (data->log_data, data->revlist, data->rcs,
  1379. pv->data))
  1380. return 1;
  1381. else
  1382. return 0;
  1383. }
  1384. /*
  1385. * Print the list of changes, not including the trunk, in reverse
  1386. * order for each branch.
  1387. */
  1388. static void
  1389. log_tree (log_data, revlist, rcs, ver)
  1390. struct log_data *log_data;
  1391. struct revlist *revlist;
  1392. RCSNode *rcs;
  1393. const char *ver;
  1394. {
  1395. Node *p;
  1396. RCSVers *vnode;
  1397. p = findnode (rcs->versions, ver);
  1398. if (p == NULL)
  1399. error (1, 0, "missing version `%s' in RCS file `%s'",
  1400. ver, rcs->path);
  1401. vnode = p->data;
  1402. if (vnode->next != NULL)
  1403. log_tree (log_data, revlist, rcs, vnode->next);
  1404. if (vnode->branches != NULL)
  1405. {
  1406. Node *head, *branch;
  1407. /* We need to do the branches in reverse order. This breaks
  1408. the List abstraction, but so does most of the branch
  1409. manipulation in rcs.c. */
  1410. head = vnode->branches->list;
  1411. for (branch = head->prev; branch != head; branch = branch->prev)
  1412. {
  1413. log_abranch (log_data, revlist, rcs, branch->key);
  1414. log_tree (log_data, revlist, rcs, branch->key);
  1415. }
  1416. }
  1417. }
  1418. /*
  1419. * Log the changes for a branch, in reverse order.
  1420. */
  1421. static void
  1422. log_abranch (log_data, revlist, rcs, ver)
  1423. struct log_data *log_data;
  1424. struct revlist *revlist;
  1425. RCSNode *rcs;
  1426. const char *ver;
  1427. {
  1428. Node *p;
  1429. RCSVers *vnode;
  1430. p = findnode (rcs->versions, ver);
  1431. if (p == NULL)
  1432. error (1, 0, "missing version `%s' in RCS file `%s'",
  1433. ver, rcs->path);
  1434. vnode = p->data;
  1435. if (vnode->next != NULL)
  1436. log_abranch (log_data, revlist, rcs, vnode->next);
  1437. log_version (log_data, revlist, rcs, vnode, 0);
  1438. }
  1439. /*
  1440. * Print the log output for a single version.
  1441. */
  1442. static void
  1443. log_version (log_data, revlist, rcs, ver, trunk)
  1444. struct log_data *log_data;
  1445. struct revlist *revlist;
  1446. RCSNode *rcs;
  1447. RCSVers *ver;
  1448. int trunk;
  1449. {
  1450. Node *p;
  1451. int year, mon, mday, hour, min, sec;
  1452. char buf[100];
  1453. Node *padd, *pdel;
  1454. if (! log_version_requested (log_data, revlist, rcs, ver))
  1455. return;
  1456. cvs_output ("----------------------------\nrevision ", 0);
  1457. cvs_output (ver->version, 0);
  1458. p = findnode (RCS_getlocks (rcs), ver->version);
  1459. if (p != NULL)
  1460. {
  1461. cvs_output ("\tlocked by: ", 0);
  1462. cvs_output (p->data, 0);
  1463. cvs_output (";", 1);
  1464. }
  1465. cvs_output ("\ndate: ", 0);
  1466. (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min,
  1467. &sec);
  1468. if (year < 1900)
  1469. year += 1900;
  1470. sprintf (buf, "%04d%c%02d%c%02d %02d:%02d:%02d",
  1471. year, datesep, mon, datesep, mday, hour, min, sec);
  1472. cvs_output (buf, 0);
  1473. cvs_output ("; author: ", 0);
  1474. cvs_output (ver->author, 0);
  1475. cvs_output ("; state: ", 0);
  1476. cvs_output (ver->state, 0);
  1477. cvs_output (";", 1);
  1478. if (! trunk)
  1479. {
  1480. padd = findnode (ver->other, ";add");
  1481. pdel = findnode (ver->other, ";delete");
  1482. }
  1483. else if (ver->next == NULL)
  1484. {
  1485. padd = NULL;
  1486. pdel = NULL;
  1487. }
  1488. else
  1489. {
  1490. Node *nextp;
  1491. RCSVers *nextver;
  1492. nextp = findnode (rcs->versions, ver->next);
  1493. if (nextp == NULL)
  1494. error (1, 0, "missing version `%s' in `%s'", ver->next,
  1495. rcs->path);
  1496. nextver = nextp->data;
  1497. pdel = findnode (nextver->other, ";add");
  1498. padd = findnode (nextver->other, ";delete");
  1499. }
  1500. if (padd != NULL)
  1501. {
  1502. assert (pdel);
  1503. cvs_output (" lines: +", 0);
  1504. cvs_output (padd->data, 0);
  1505. cvs_output (" -", 2);
  1506. cvs_output (pdel->data, 0);
  1507. }
  1508. if (ver->branches != NULL)
  1509. {
  1510. cvs_output ("\nbranches:", 0);
  1511. walklist (ver->branches, log_branch, (void *) NULL);
  1512. }
  1513. cvs_output ("\n", 1);
  1514. p = findnode (ver->other, "log");
  1515. /* The p->date == NULL case is the normal one for an empty log
  1516. message (rcs-14 in sanity.sh). I don't think the case where
  1517. p->data is "" can happen (getrcskey in rcs.c checks for an
  1518. empty string and set the value to NULL in that case). My guess
  1519. would be the p == NULL case would mean an RCS file which was
  1520. missing the "log" keyword (which is illegal according to
  1521. rcsfile.5). */
  1522. if (p == NULL || p->data == NULL || *(char *)p->data == '\0')
  1523. cvs_output ("*** empty log message ***\n", 0);
  1524. else
  1525. {
  1526. /* FIXME: Technically, the log message could contain a null
  1527. byte. */
  1528. cvs_output (p->data, 0);
  1529. if (((char *)p->data)[strlen (p->data) - 1] != '\n')
  1530. cvs_output ("\n", 1);
  1531. }
  1532. }
  1533. /*
  1534. * Output a branch version. This is called via walklist.
  1535. */
  1536. /*ARGSUSED*/
  1537. static int
  1538. log_branch (p, closure)
  1539. Node *p;
  1540. void *closure;
  1541. {
  1542. cvs_output (" ", 2);
  1543. if ((numdots (p->key) & 1) == 0)
  1544. cvs_output (p->key, 0);
  1545. else
  1546. {
  1547. char *f, *cp;
  1548. f = xstrdup (p->key);
  1549. cp = strrchr (f, '.');
  1550. *cp = '\0';
  1551. cvs_output (f, 0);
  1552. free (f);
  1553. }
  1554. cvs_output (";", 1);
  1555. return 0;
  1556. }
  1557. /*
  1558. * Print a warm fuzzy message
  1559. */
  1560. /* ARGSUSED */
  1561. static Dtype
  1562. log_dirproc (callerdat, dir, repository, update_dir, entries)
  1563. void *callerdat;
  1564. const char *dir;
  1565. const char *repository;
  1566. const char *update_dir;
  1567. List *entries;
  1568. {
  1569. if (!isdir (dir))
  1570. return (R_SKIP_ALL);
  1571. if (!quiet)
  1572. error (0, 0, "Logging %s", update_dir);
  1573. return (R_PROCESS);
  1574. }
  1575. /*
  1576. * Compare versions. This is taken from RCS compartial.
  1577. */
  1578. static int
  1579. version_compare (v1, v2, len)
  1580. const char *v1;
  1581. const char *v2;
  1582. int len;
  1583. {
  1584. while (1)
  1585. {
  1586. int d1, d2, r;
  1587. if (*v1 == '\0')
  1588. return 1;
  1589. if (*v2 == '\0')
  1590. return -1;
  1591. while (*v1 == '0')
  1592. ++v1;
  1593. for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1)
  1594. ;
  1595. while (*v2 == '0')
  1596. ++v2;
  1597. for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2)
  1598. ;
  1599. if (d1 != d2)
  1600. return d1 < d2 ? -1 : 1;
  1601. r = memcmp (v1, v2, d1);
  1602. if (r != 0)
  1603. return r;
  1604. --len;
  1605. if (len == 0)
  1606. return 0;
  1607. v1 += d1;
  1608. v2 += d1;
  1609. if (*v1 == '.')
  1610. ++v1;
  1611. if (*v2 == '.')
  1612. ++v2;
  1613. }
  1614. }