/contrib/cvs/src/history.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 1666 lines · 1114 code · 136 blank · 416 comment · 324 complexity · 3d1526df52abc22e814b913db2dcf2ca MD5 · raw file

  1. /*
  2. * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2, or (at your option)
  7. * any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. */
  14. /* **************** History of Users and Module ****************
  15. *
  16. * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
  17. *
  18. * On For each Tag, Add, Checkout, Commit, Update or Release command,
  19. * one line of text is written to a History log.
  20. *
  21. * X date | user | CurDir | special | rev(s) | argument '\n'
  22. *
  23. * where: [The spaces in the example line above are not in the history file.]
  24. *
  25. * X is a single character showing the type of event:
  26. * T "Tag" cmd.
  27. * O "Checkout" cmd.
  28. * E "Export" cmd.
  29. * F "Release" cmd.
  30. * W "Update" cmd - No User file, Remove from Entries file.
  31. * U "Update" cmd - File was checked out over User file.
  32. * P "Update" cmd - User file was patched.
  33. * G "Update" cmd - File was merged successfully.
  34. * C "Update" cmd - File was merged and shows overlaps.
  35. * M "Commit" cmd - "Modified" file.
  36. * A "Commit" cmd - "Added" file.
  37. * R "Commit" cmd - "Removed" file.
  38. *
  39. * date is a fixed length 8-char hex representation of a Unix time_t.
  40. * [Starting here, variable fields are delimited by '|' chars.]
  41. *
  42. * user is the username of the person who typed the command.
  43. *
  44. * CurDir The directory where the action occurred. This should be the
  45. * absolute path of the directory which is at the same level as
  46. * the "Repository" field (for W,U,P,G,C & M,A,R).
  47. *
  48. * Repository For record types [W,U,P,G,C,M,A,R] this field holds the
  49. * repository read from the administrative data where the
  50. * command was typed.
  51. * T "A" --> New Tag, "D" --> Delete Tag
  52. * Otherwise it is the Tag or Date to modify.
  53. * O,F,E A "" (null field)
  54. *
  55. * rev(s) Revision number or tag.
  56. * T The Tag to apply.
  57. * O,E The Tag or Date, if specified, else "" (null field).
  58. * F "" (null field)
  59. * W The Tag or Date, if specified, else "" (null field).
  60. * U,P The Revision checked out over the User file.
  61. * G,C The Revision(s) involved in merge.
  62. * M,A,R RCS Revision affected.
  63. *
  64. * argument The module (for [TOEF]) or file (for [WUPGCMAR]) affected.
  65. *
  66. *
  67. *** Report categories: "User" and "Since" modifiers apply to all reports.
  68. * [For "sort" ordering see the "sort_order" routine.]
  69. *
  70. * Extract list of record types
  71. *
  72. * -e, -x [TOEFWUPGCMAR]
  73. *
  74. * Extracted records are simply printed, No analysis is performed.
  75. * All "field" modifiers apply. -e chooses all types.
  76. *
  77. * Checked 'O'ut modules
  78. *
  79. * -o, -w
  80. * Checked out modules. 'F' and 'O' records are examined and if
  81. * the last record for a repository/file is an 'O', a line is
  82. * printed. "-w" forces the "working dir" to be used in the
  83. * comparison instead of the repository.
  84. *
  85. * Committed (Modified) files
  86. *
  87. * -c, -l, -w
  88. * All 'M'odified, 'A'dded and 'R'emoved records are examined.
  89. * "Field" modifiers apply. -l forces a sort by file within user
  90. * and shows only the last modifier. -w works as in Checkout.
  91. *
  92. * Warning: Be careful with what you infer from the output of
  93. * "cvs hi -c -l". It means the last time *you*
  94. * changed the file, not the list of files for which
  95. * you were the last changer!!!
  96. *
  97. * Module history for named modules.
  98. * -m module, -l
  99. *
  100. * This is special. If one or more modules are specified, the
  101. * module names are remembered and the files making up the
  102. * modules are remembered. Only records matching exactly those
  103. * files and repositories are shown. Sorting by "module", then
  104. * filename, is implied. If -l ("last modified") is specified,
  105. * then "update" records (types WUPCG), tag and release records
  106. * are ignored and the last (by date) "modified" record.
  107. *
  108. * TAG history
  109. *
  110. * -T All Tag records are displayed.
  111. *
  112. *** Modifiers.
  113. *
  114. * Since ... [All records contain a timestamp, so any report
  115. * category can be limited by date.]
  116. *
  117. * -D date - The "date" is parsed into a Unix "time_t" and
  118. * records with an earlier time stamp are ignored.
  119. * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
  120. * you use this option, every file is searched for the
  121. * indicated rev/tag.
  122. * -t tag - The "tag" is searched for in the history file and no
  123. * record is displayed before the tag is found. An
  124. * error is printed if the tag is never found.
  125. * -b string - Records are printed only back to the last reference
  126. * to the string in the "module", "file" or
  127. * "repository" fields.
  128. *
  129. * Field Selections [Simple comparisons on existing fields. All field
  130. * selections are repeatable.]
  131. *
  132. * -a - All users.
  133. * -u user - If no user is given and '-a' is not given, only
  134. * records for the user typing the command are shown.
  135. * ==> If -a or -u is not specified, just use "self".
  136. *
  137. * -f filematch - Only records in which the "file" field contains the
  138. * string "filematch" are considered.
  139. *
  140. * -p repository - Only records in which the "repository" string is a
  141. * prefix of the "repos" field are considered.
  142. *
  143. * -n modulename - Only records which contain "modulename" in the
  144. * "module" field are considered.
  145. *
  146. *
  147. * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
  148. *
  149. *** Checked out files for username. (default self, e.g. "dgg")
  150. * cvs hi [equivalent to: "cvs hi -o -u dgg"]
  151. * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
  152. * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
  153. *
  154. *** Committed (modified) files from the beginning of the file.
  155. * cvs hi -c [-u user]
  156. *
  157. *** Committed (modified) files since Midnight, January 1, 1990:
  158. * cvs hi -c -D 'Jan 1 1990' [-u user]
  159. *
  160. *** Committed (modified) files since tag "TAG" was stored in the history file:
  161. * cvs hi -c -t TAG [-u user]
  162. *
  163. *** Committed (modified) files since tag "TAG" was placed on the files:
  164. * cvs hi -c -r TAG [-u user]
  165. *
  166. *** Who last committed file/repository X?
  167. * cvs hi -c -l -[fp] X
  168. *
  169. *** Modified files since tag/date/file/repos?
  170. * cvs hi -c {-r TAG | -D Date | -b string}
  171. *
  172. *** Tag history
  173. * cvs hi -T
  174. *
  175. *** History of file/repository/module X.
  176. * cvs hi -[fpn] X
  177. *
  178. *** History of user "user".
  179. * cvs hi -e -u user
  180. *
  181. *** Dump (eXtract) specified record types
  182. * cvs hi -x [TOEFWUPGCMAR]
  183. *
  184. *
  185. * FUTURE: J[Join], I[Import] (Not currently implemented.)
  186. *
  187. */
  188. #include "cvs.h"
  189. #include "history.h"
  190. #include "savecwd.h"
  191. static struct hrec
  192. {
  193. char *type; /* Type of record (In history record) */
  194. char *user; /* Username (In history record) */
  195. char *dir; /* "Compressed" Working dir (In history record) */
  196. char *repos; /* (Tag is special.) Repository (In history record) */
  197. char *rev; /* Revision affected (In history record) */
  198. char *file; /* Filename (In history record) */
  199. char *end; /* Ptr into repository to copy at end of workdir */
  200. char *mod; /* The module within which the file is contained */
  201. time_t date; /* Calculated from date stored in record */
  202. long idx; /* Index of record, for "stable" sort. */
  203. } *hrec_head;
  204. static long hrec_idx;
  205. static void fill_hrec PROTO((char *line, struct hrec * hr));
  206. static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
  207. static int select_hrec PROTO((struct hrec * hr));
  208. static int sort_order PROTO((const PTR l, const PTR r));
  209. static int within PROTO((char *find, char *string));
  210. static void expand_modules PROTO((void));
  211. static void read_hrecs PROTO((char *fname));
  212. static void report_hrecs PROTO((void));
  213. static void save_file PROTO((char *dir, char *name, char *module));
  214. static void save_module PROTO((char *module));
  215. static void save_user PROTO((char *name));
  216. #define USER_INCREMENT 2
  217. #define FILE_INCREMENT 128
  218. #define MODULE_INCREMENT 5
  219. #define HREC_INCREMENT 128
  220. static short report_count;
  221. static short extract;
  222. static short extract_all;
  223. static short v_checkout;
  224. static short modified;
  225. static short tag_report;
  226. static short module_report;
  227. static short working;
  228. static short last_entry;
  229. static short all_users;
  230. static short user_sort;
  231. static short repos_sort;
  232. static short file_sort;
  233. static short module_sort;
  234. static short tz_local;
  235. static time_t tz_seconds_east_of_GMT;
  236. static char *tz_name = "+0000";
  237. char *logHistory;
  238. /* -r, -t, or -b options, malloc'd. These are "" if the option in
  239. question is not specified or is overridden by another option. The
  240. main reason for using "" rather than NULL is historical. Together
  241. with since_date, these are a mutually exclusive set; one overrides the
  242. others. */
  243. static char *since_rev;
  244. static char *since_tag;
  245. static char *backto;
  246. /* -D option, or 0 if not specified. RCS format. */
  247. static char * since_date;
  248. static struct hrec *last_since_tag;
  249. static struct hrec *last_backto;
  250. /* Record types to look for, malloc'd. Probably could be statically
  251. allocated, but only if we wanted to check for duplicates more than
  252. we do. */
  253. static char *rec_types;
  254. static size_t hrec_count;
  255. static size_t hrec_max;
  256. static char **user_list; /* Ptr to array of ptrs to user names */
  257. static size_t user_max; /* Number of elements allocated */
  258. static size_t user_count; /* Number of elements used */
  259. static struct file_list_str
  260. {
  261. char *l_file;
  262. char *l_module;
  263. } *file_list; /* Ptr to array file name structs */
  264. static size_t file_max; /* Number of elements allocated */
  265. static size_t file_count; /* Number of elements used */
  266. static char **mod_list; /* Ptr to array of ptrs to module names */
  267. static size_t mod_max; /* Number of elements allocated */
  268. static size_t mod_count; /* Number of elements used */
  269. static char *histfile; /* Ptr to the history file name */
  270. /* This is pretty unclear. First of all, separating "flags" vs.
  271. "options" (I think the distinction is that "options" take arguments)
  272. is nonstandard, and not something we do elsewhere in CVS. Second of
  273. all, what does "reports" mean? I think it means that you can only
  274. supply one of those options, but "reports" hardly has that meaning in
  275. a self-explanatory way. */
  276. static const char *const history_usg[] =
  277. {
  278. "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
  279. " Reports:\n",
  280. " -T Produce report on all TAGs\n",
  281. " -c Committed (Modified) files\n",
  282. " -o Checked out modules\n",
  283. " -m <module> Look for specified module (repeatable)\n",
  284. " -x [" ALL_HISTORY_REC_TYPES "] Extract by record type\n",
  285. " -e Everything (same as -x, but all record types)\n",
  286. " Flags:\n",
  287. " -a All users (Default is self)\n",
  288. " -l Last modified (committed or modified report)\n",
  289. " -w Working directory must match\n",
  290. " Options:\n",
  291. " -D <date> Since date (Many formats)\n",
  292. " -b <str> Back to record with str in module/file/repos field\n",
  293. " -f <file> Specified file (same as command line) (repeatable)\n",
  294. " -n <modulename> In module (repeatable)\n",
  295. " -p <repos> In repository (repeatable)\n",
  296. " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
  297. " -t <tag> Since tag record placed in history file (by anyone).\n",
  298. " -u <user> For user name (repeatable)\n",
  299. " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
  300. NULL};
  301. /* Sort routine for qsort:
  302. - If a user is selected at all, sort it first. User-within-file is useless.
  303. - If a module was selected explicitly, sort next on module.
  304. - Then sort by file. "File" is "repository/file" unless "working" is set,
  305. then it is "workdir/file". (Revision order should always track date.)
  306. - Always sort timestamp last.
  307. */
  308. static int
  309. sort_order (l, r)
  310. const PTR l;
  311. const PTR r;
  312. {
  313. int i;
  314. const struct hrec *left = (const struct hrec *) l;
  315. const struct hrec *right = (const struct hrec *) r;
  316. if (user_sort) /* If Sort by username, compare users */
  317. {
  318. if ((i = strcmp (left->user, right->user)) != 0)
  319. return (i);
  320. }
  321. if (module_sort) /* If sort by modules, compare module names */
  322. {
  323. if (left->mod && right->mod)
  324. if ((i = strcmp (left->mod, right->mod)) != 0)
  325. return (i);
  326. }
  327. if (repos_sort) /* If sort by repository, compare them. */
  328. {
  329. if ((i = strcmp (left->repos, right->repos)) != 0)
  330. return (i);
  331. }
  332. if (file_sort) /* If sort by filename, compare files, NOT dirs. */
  333. {
  334. if ((i = strcmp (left->file, right->file)) != 0)
  335. return (i);
  336. if (working)
  337. {
  338. if ((i = strcmp (left->dir, right->dir)) != 0)
  339. return (i);
  340. if ((i = strcmp (left->end, right->end)) != 0)
  341. return (i);
  342. }
  343. }
  344. /*
  345. * By default, sort by date, time
  346. * XXX: This fails after 2030 when date slides into sign bit
  347. */
  348. if ((i = ((long) (left->date) - (long) (right->date))) != 0)
  349. return (i);
  350. /* For matching dates, keep the sort stable by using record index */
  351. return (left->idx - right->idx);
  352. }
  353. int
  354. history (argc, argv)
  355. int argc;
  356. char **argv;
  357. {
  358. int i, c;
  359. char *fname;
  360. if (argc == -1)
  361. usage (history_usg);
  362. since_rev = xstrdup ("");
  363. since_tag = xstrdup ("");
  364. backto = xstrdup ("");
  365. rec_types = xstrdup ("");
  366. optind = 0;
  367. while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
  368. {
  369. switch (c)
  370. {
  371. case 'T': /* Tag list */
  372. report_count++;
  373. tag_report++;
  374. break;
  375. case 'a': /* For all usernames */
  376. all_users++;
  377. break;
  378. case 'c':
  379. report_count++;
  380. modified = 1;
  381. break;
  382. case 'e':
  383. report_count++;
  384. extract_all++;
  385. free (rec_types);
  386. rec_types = xstrdup (ALL_HISTORY_REC_TYPES);
  387. break;
  388. case 'l': /* Find Last file record */
  389. last_entry = 1;
  390. break;
  391. case 'o':
  392. report_count++;
  393. v_checkout = 1;
  394. break;
  395. case 'w': /* Match Working Dir (CurDir) fields */
  396. working = 1;
  397. break;
  398. case 'X': /* Undocumented debugging flag */
  399. #ifdef DEBUG
  400. histfile = optarg;
  401. #endif
  402. break;
  403. case 'D': /* Since specified date */
  404. if (*since_rev || *since_tag || *backto)
  405. {
  406. error (0, 0, "date overriding rev/tag/backto");
  407. *since_rev = *since_tag = *backto = '\0';
  408. }
  409. since_date = Make_Date (optarg);
  410. break;
  411. case 'b': /* Since specified file/Repos */
  412. if (since_date || *since_rev || *since_tag)
  413. {
  414. error (0, 0, "backto overriding date/rev/tag");
  415. *since_rev = *since_tag = '\0';
  416. if (since_date != NULL)
  417. free (since_date);
  418. since_date = NULL;
  419. }
  420. free (backto);
  421. backto = xstrdup (optarg);
  422. break;
  423. case 'f': /* For specified file */
  424. save_file (NULL, optarg, NULL);
  425. break;
  426. case 'm': /* Full module report */
  427. if (!module_report++) report_count++;
  428. /* fall through */
  429. case 'n': /* Look for specified module */
  430. save_module (optarg);
  431. break;
  432. case 'p': /* For specified directory */
  433. save_file (optarg, NULL, NULL);
  434. break;
  435. case 'r': /* Since specified Tag/Rev */
  436. if (since_date || *since_tag || *backto)
  437. {
  438. error (0, 0, "rev overriding date/tag/backto");
  439. *since_tag = *backto = '\0';
  440. if (since_date != NULL)
  441. free (since_date);
  442. since_date = NULL;
  443. }
  444. free (since_rev);
  445. since_rev = xstrdup (optarg);
  446. break;
  447. case 't': /* Since specified Tag/Rev */
  448. if (since_date || *since_rev || *backto)
  449. {
  450. error (0, 0, "tag overriding date/marker/file/repos");
  451. *since_rev = *backto = '\0';
  452. if (since_date != NULL)
  453. free (since_date);
  454. since_date = NULL;
  455. }
  456. free (since_tag);
  457. since_tag = xstrdup (optarg);
  458. break;
  459. case 'u': /* For specified username */
  460. save_user (optarg);
  461. break;
  462. case 'x':
  463. report_count++;
  464. extract++;
  465. {
  466. char *cp;
  467. for (cp = optarg; *cp; cp++)
  468. if (!strchr (ALL_HISTORY_REC_TYPES, *cp))
  469. error (1, 0, "%c is not a valid report type", *cp);
  470. }
  471. free (rec_types);
  472. rec_types = xstrdup (optarg);
  473. break;
  474. case 'z':
  475. tz_local =
  476. (optarg[0] == 'l' || optarg[0] == 'L')
  477. && (optarg[1] == 't' || optarg[1] == 'T')
  478. && !optarg[2];
  479. if (tz_local)
  480. tz_name = optarg;
  481. else
  482. {
  483. /*
  484. * Convert a known time with the given timezone to time_t.
  485. * Use the epoch + 23 hours, so timezones east of GMT work.
  486. */
  487. static char f[] = "1/1/1970 23:00 %s";
  488. char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
  489. time_t t;
  490. sprintf (buf, f, optarg);
  491. t = get_date (buf, (struct timeb *) NULL);
  492. free (buf);
  493. if (t == (time_t) -1)
  494. error (0, 0, "%s is not a known time zone", optarg);
  495. else
  496. {
  497. /*
  498. * Convert to seconds east of GMT, removing the
  499. * 23-hour offset mentioned above.
  500. */
  501. tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t;
  502. tz_name = optarg;
  503. }
  504. }
  505. break;
  506. case '?':
  507. default:
  508. usage (history_usg);
  509. break;
  510. }
  511. }
  512. argc -= optind;
  513. argv += optind;
  514. for (i = 0; i < argc; i++)
  515. save_file (NULL, argv[i], NULL);
  516. /* ================ Now analyze the arguments a bit */
  517. if (!report_count)
  518. v_checkout++;
  519. else if (report_count > 1)
  520. error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
  521. #ifdef CLIENT_SUPPORT
  522. if (current_parsed_root->isremote)
  523. {
  524. struct file_list_str *f1;
  525. char **mod;
  526. /* We're the client side. Fire up the remote server. */
  527. start_server ();
  528. ign_setup ();
  529. if (tag_report)
  530. send_arg("-T");
  531. if (all_users)
  532. send_arg("-a");
  533. if (modified)
  534. send_arg("-c");
  535. if (last_entry)
  536. send_arg("-l");
  537. if (v_checkout)
  538. send_arg("-o");
  539. if (working)
  540. send_arg("-w");
  541. if (histfile)
  542. send_arg("-X");
  543. if (since_date)
  544. client_senddate (since_date);
  545. if (backto[0] != '\0')
  546. option_with_arg ("-b", backto);
  547. for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
  548. {
  549. if (f1->l_file[0] == '*')
  550. option_with_arg ("-p", f1->l_file + 1);
  551. else
  552. option_with_arg ("-f", f1->l_file);
  553. }
  554. if (module_report)
  555. send_arg("-m");
  556. for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
  557. option_with_arg ("-n", *mod);
  558. if (*since_rev)
  559. option_with_arg ("-r", since_rev);
  560. if (*since_tag)
  561. option_with_arg ("-t", since_tag);
  562. for (mod = user_list; mod < &user_list[user_count]; ++mod)
  563. option_with_arg ("-u", *mod);
  564. if (extract_all)
  565. send_arg("-e");
  566. if (extract)
  567. option_with_arg ("-x", rec_types);
  568. option_with_arg ("-z", tz_name);
  569. send_to_server ("history\012", 0);
  570. return get_responses_and_close ();
  571. }
  572. #endif
  573. if (all_users)
  574. save_user ("");
  575. if (mod_list)
  576. expand_modules ();
  577. if (tag_report)
  578. {
  579. if (!strchr (rec_types, 'T'))
  580. {
  581. rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
  582. (void) strcat (rec_types, "T");
  583. }
  584. }
  585. else if (extract || extract_all)
  586. {
  587. if (user_list)
  588. user_sort++;
  589. }
  590. else if (modified)
  591. {
  592. free (rec_types);
  593. rec_types = xstrdup ("MAR");
  594. /*
  595. * If the user has not specified a date oriented flag ("Since"), sort
  596. * by Repository/file before date. Default is "just" date.
  597. */
  598. if (last_entry
  599. || (!since_date && !*since_rev && !*since_tag && !*backto))
  600. {
  601. repos_sort++;
  602. file_sort++;
  603. /*
  604. * If we are not looking for last_modified and the user specified
  605. * one or more users to look at, sort by user before filename.
  606. */
  607. if (!last_entry && user_list)
  608. user_sort++;
  609. }
  610. }
  611. else if (module_report)
  612. {
  613. free (rec_types);
  614. rec_types = xstrdup (last_entry ? "OMAR" : ALL_HISTORY_REC_TYPES);
  615. module_sort++;
  616. repos_sort++;
  617. file_sort++;
  618. working = 0; /* User's workdir doesn't count here */
  619. }
  620. else
  621. /* Must be "checkout" or default */
  622. {
  623. free (rec_types);
  624. rec_types = xstrdup ("OF");
  625. /* See comments in "modified" above */
  626. if (!last_entry && user_list)
  627. user_sort++;
  628. if (last_entry
  629. || (!since_date && !*since_rev && !*since_tag && !*backto))
  630. file_sort++;
  631. }
  632. /* If no users were specified, use self (-a saves a universal ("") user) */
  633. if (!user_list)
  634. save_user (getcaller ());
  635. /* If we're looking back to a Tag value, must consider "Tag" records */
  636. if (*since_tag && !strchr (rec_types, 'T'))
  637. {
  638. rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
  639. (void) strcat (rec_types, "T");
  640. }
  641. if (histfile)
  642. fname = xstrdup (histfile);
  643. else
  644. {
  645. fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
  646. + sizeof (CVSROOTADM_HISTORY) + 10);
  647. (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
  648. CVSROOTADM, CVSROOTADM_HISTORY);
  649. }
  650. read_hrecs (fname);
  651. if(hrec_count>0)
  652. {
  653. qsort ((PTR) hrec_head, hrec_count,
  654. sizeof (struct hrec), sort_order);
  655. }
  656. report_hrecs ();
  657. free (fname);
  658. if (since_date != NULL)
  659. free (since_date);
  660. free (since_rev);
  661. free (since_tag);
  662. free (backto);
  663. free (rec_types);
  664. return (0);
  665. }
  666. void
  667. history_write (type, update_dir, revs, name, repository)
  668. int type;
  669. const char *update_dir;
  670. const char *revs;
  671. const char *name;
  672. const char *repository;
  673. {
  674. char *fname;
  675. char *workdir;
  676. char *username = getcaller ();
  677. int fd;
  678. char *line;
  679. char *slash = "", *cp;
  680. const char *cp2, *repos;
  681. int i;
  682. static char *tilde = "";
  683. static char *PrCurDir = NULL;
  684. if (logoff) /* History is turned off by noexec or
  685. * readonlyfs.
  686. */
  687. return;
  688. if (strchr (logHistory, type) == NULL)
  689. return;
  690. fname = xmalloc (strlen (current_parsed_root->directory)
  691. + sizeof (CVSROOTADM)
  692. + sizeof (CVSROOTADM_HISTORY) + 3);
  693. (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
  694. CVSROOTADM, CVSROOTADM_HISTORY);
  695. /* turn off history logging if the history file does not exist */
  696. /* FIXME: This should check for write permissions instead. This way,
  697. * O_CREATE could be added back into the call to open() below and
  698. * there would be no race condition involved in log rotation.
  699. *
  700. * Note that the new method of turning off logging would be either via
  701. * the CVSROOT/config file (probably the quicker method, but would need
  702. * to be added, or at least checked for, too) or by creating a dummy
  703. * history file with 0444 permissions.
  704. */
  705. if (!isfile (fname))
  706. {
  707. logoff = 1;
  708. goto out;
  709. }
  710. if (trace)
  711. fprintf (stderr, "%s-> fopen(%s,a)\n",
  712. CLIENT_SERVER_STR, fname);
  713. if (noexec)
  714. goto out;
  715. if (!history_lock (current_parsed_root->directory))
  716. /* history_lock() will already have printed an error on failure. */
  717. goto out;
  718. fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | OPEN_BINARY, 0666);
  719. if (fd < 0)
  720. {
  721. if (! really_quiet)
  722. {
  723. error (0, errno, "warning: cannot write to history file %s",
  724. fname);
  725. }
  726. goto out;
  727. }
  728. repos = Short_Repository (repository);
  729. if (!PrCurDir)
  730. {
  731. char *pwdir;
  732. pwdir = get_homedir ();
  733. PrCurDir = CurDir;
  734. if (pwdir != NULL)
  735. {
  736. /* Assumes neither CurDir nor pwdir ends in '/' */
  737. i = strlen (pwdir);
  738. if (!strncmp (CurDir, pwdir, i))
  739. {
  740. PrCurDir += i; /* Point to '/' separator */
  741. tilde = "~";
  742. }
  743. else
  744. {
  745. /* Try harder to find a "homedir" */
  746. struct saved_cwd cwd;
  747. char *homedir;
  748. if (save_cwd (&cwd))
  749. error_exit ();
  750. if (CVS_CHDIR (pwdir) < 0 || (homedir = xgetwd ()) == NULL)
  751. homedir = pwdir;
  752. if (restore_cwd (&cwd, NULL))
  753. error_exit ();
  754. free_cwd (&cwd);
  755. i = strlen (homedir);
  756. if (!strncmp (CurDir, homedir, i))
  757. {
  758. PrCurDir += i; /* Point to '/' separator */
  759. tilde = "~";
  760. }
  761. if (homedir != pwdir)
  762. free (homedir);
  763. }
  764. }
  765. }
  766. if (type == 'T')
  767. {
  768. repos = update_dir;
  769. update_dir = "";
  770. }
  771. else if (update_dir && *update_dir)
  772. slash = "/";
  773. else
  774. update_dir = "";
  775. workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash)
  776. + strlen (update_dir) + 10);
  777. (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
  778. /*
  779. * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
  780. * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
  781. *
  782. * "$workdir/$name" is the working file name.
  783. * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
  784. *
  785. * First, note that the history format was intended to save space, not
  786. * to be human readable.
  787. *
  788. * The working file directory ("workdir") and the Repository ("repos")
  789. * usually end with the same one or more directory elements. To avoid
  790. * duplication (and save space), the "workdir" field ends with
  791. * an integer offset into the "repos" field. This offset indicates the
  792. * beginning of the "tail" of "repos", after which all characters are
  793. * duplicates.
  794. *
  795. * In other words, if the "workdir" field has a '*' (a very stupid thing
  796. * to put in a filename) in it, then every thing following the last '*'
  797. * is a hex offset into "repos" of the first character from "repos" to
  798. * append to "workdir" to finish the pathname.
  799. *
  800. * It might be easier to look at an example:
  801. *
  802. * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  803. *
  804. * Indicates that the workdir is really "~/work/cvs/examples", saving
  805. * 10 characters, where "~/work*d" would save 6 characters and mean that
  806. * the workdir is really "~/work/examples". It will mean more on
  807. * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
  808. *
  809. * "workdir" is always an absolute pathname (~/xxx is an absolute path)
  810. * "repos" is always a relative pathname. So we can assume that we will
  811. * never run into the top of "workdir" -- there will always be a '/' or
  812. * a '~' at the head of "workdir" that is not matched by anything in
  813. * "repos". On the other hand, we *can* run off the top of "repos".
  814. *
  815. * Only "compress" if we save characters.
  816. */
  817. if (!repos)
  818. repos = "";
  819. cp = workdir + strlen (workdir) - 1;
  820. cp2 = repos + strlen (repos) - 1;
  821. for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
  822. i++;
  823. if (i > 2)
  824. {
  825. i = strlen (repos) - i;
  826. (void) sprintf ((cp + 1), "*%x", i);
  827. }
  828. if (!revs)
  829. revs = "";
  830. line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
  831. + strlen (revs) + strlen (name) + 100);
  832. sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n",
  833. type, (long) time ((time_t *) NULL),
  834. username, workdir, repos, revs, name);
  835. /* Lessen some race conditions on non-Posix-compliant hosts. */
  836. if (lseek (fd, (off_t) 0, SEEK_END) == -1)
  837. error (1, errno, "cannot seek to end of history file: %s", fname);
  838. if (write (fd, line, strlen (line)) < 0)
  839. error (1, errno, "cannot write to history file: %s", fname);
  840. free (line);
  841. if (close (fd) != 0)
  842. error (1, errno, "cannot close history file: %s", fname);
  843. free (workdir);
  844. out:
  845. clear_history_lock ();
  846. free (fname);
  847. }
  848. /*
  849. * save_user() adds a user name to the user list to select. Zero-length
  850. * username ("") matches any user.
  851. */
  852. static void
  853. save_user (name)
  854. char *name;
  855. {
  856. if (user_count == user_max)
  857. {
  858. user_max = xsum (user_max, USER_INCREMENT);
  859. if (user_count == user_max
  860. || size_overflow_p (xtimes (user_max, sizeof (char *))))
  861. {
  862. error (0, 0, "save_user: too many users");
  863. return;
  864. }
  865. user_list = xrealloc (user_list, xtimes (user_max, sizeof (char *)));
  866. }
  867. user_list[user_count++] = xstrdup (name);
  868. }
  869. /*
  870. * save_file() adds file name and associated module to the file list to select.
  871. *
  872. * If "dir" is null, store a file name as is.
  873. * If "name" is null, store a directory name with a '*' on the front.
  874. * Else, store concatenated "dir/name".
  875. *
  876. * Later, in the "select" stage:
  877. * - if it starts with '*', it is prefix-matched against the repository.
  878. * - if it has a '/' in it, it is matched against the repository/file.
  879. * - else it is matched against the file name.
  880. */
  881. static void
  882. save_file (dir, name, module)
  883. char *dir;
  884. char *name;
  885. char *module;
  886. {
  887. char *cp;
  888. struct file_list_str *fl;
  889. if (file_count == file_max)
  890. {
  891. file_max = xsum (file_max, FILE_INCREMENT);
  892. if (file_count == file_max
  893. || size_overflow_p (xtimes (file_max, sizeof (*fl))))
  894. {
  895. error (0, 0, "save_file: too many files");
  896. return;
  897. }
  898. file_list = xrealloc (file_list, xtimes (file_max, sizeof (*fl)));
  899. }
  900. fl = &file_list[file_count++];
  901. fl->l_file = cp = xmalloc (dir ? strlen (dir) : 0
  902. + name ? strlen (name) : 0
  903. + 2);
  904. fl->l_module = module;
  905. if (dir && *dir)
  906. {
  907. if (name && *name)
  908. {
  909. (void) strcpy (cp, dir);
  910. (void) strcat (cp, "/");
  911. (void) strcat (cp, name);
  912. }
  913. else
  914. {
  915. *cp++ = '*';
  916. (void) strcpy (cp, dir);
  917. }
  918. }
  919. else
  920. {
  921. if (name && *name)
  922. {
  923. (void) strcpy (cp, name);
  924. }
  925. else
  926. {
  927. error (0, 0, "save_file: null dir and file name");
  928. }
  929. }
  930. }
  931. static void
  932. save_module (module)
  933. char *module;
  934. {
  935. if (mod_count == mod_max)
  936. {
  937. mod_max = xsum (mod_max, MODULE_INCREMENT);
  938. if (mod_count == mod_max
  939. || size_overflow_p (xtimes (mod_max, sizeof (char *))))
  940. {
  941. error (0, 0, "save_module: too many modules");
  942. return;
  943. }
  944. mod_list = xrealloc (mod_list, xtimes (mod_max, sizeof (char *)));
  945. }
  946. mod_list[mod_count++] = xstrdup (module);
  947. }
  948. static void
  949. expand_modules ()
  950. {
  951. }
  952. /* fill_hrec
  953. *
  954. * Take a ptr to 7-part history line, ending with a newline, for example:
  955. *
  956. * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  957. *
  958. * Split it into 7 parts and drop the parts into a "struct hrec".
  959. * Return a pointer to the character following the newline.
  960. *
  961. */
  962. #define NEXT_BAR(here) do { \
  963. while (isspace(*line)) line++; \
  964. hr->here = line; \
  965. while ((c = *line++) && c != '|') ; \
  966. if (!c) return; line[-1] = '\0'; \
  967. } while (0)
  968. static void
  969. fill_hrec (line, hr)
  970. char *line;
  971. struct hrec *hr;
  972. {
  973. char *cp;
  974. int c;
  975. hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
  976. hr->end = hr->mod = NULL;
  977. hr->date = -1;
  978. hr->idx = ++hrec_idx;
  979. while (isspace ((unsigned char) *line))
  980. line++;
  981. hr->type = line++;
  982. hr->date = strtoul (line, &cp, 16);
  983. if (cp == line || *cp != '|')
  984. return;
  985. line = cp + 1;
  986. NEXT_BAR (user);
  987. NEXT_BAR (dir);
  988. if ((cp = strrchr (hr->dir, '*')) != NULL)
  989. {
  990. *cp++ = '\0';
  991. hr->end = line + strtoul (cp, NULL, 16);
  992. }
  993. else
  994. hr->end = line - 1; /* A handy pointer to '\0' */
  995. NEXT_BAR (repos);
  996. NEXT_BAR (rev);
  997. if (strchr ("FOET", *(hr->type)))
  998. hr->mod = line;
  999. NEXT_BAR (file);
  1000. }
  1001. #ifndef STAT_BLOCKSIZE
  1002. #if HAVE_STRUCT_STAT_ST_BLKSIZE
  1003. #define STAT_BLOCKSIZE(s) (s).st_blksize
  1004. #else
  1005. #define STAT_BLOCKSIZE(s) (4 * 1024)
  1006. #endif
  1007. #endif
  1008. /* read_hrecs's job is to read the history file and fill in all the "hrec"
  1009. * (history record) array elements with the ones we need to print.
  1010. *
  1011. * Logic:
  1012. * - Read a block from the file.
  1013. * - Walk through the block parsing line into hr records.
  1014. * - if the hr isn't used, free its strings, if it is, bump the hrec counter
  1015. * - at the end of a block, copy the end of the current block to the start
  1016. * of space for the next block, then read in the next block. If we get less
  1017. * than the whole block, we're done.
  1018. */
  1019. static void
  1020. read_hrecs (fname)
  1021. char *fname;
  1022. {
  1023. unsigned char *cpstart, *cpend, *cp, *nl;
  1024. char *hrline;
  1025. int i;
  1026. int fd;
  1027. struct stat st_buf;
  1028. if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
  1029. error (1, errno, "cannot open history file: %s", fname);
  1030. if (fstat (fd, &st_buf) < 0)
  1031. error (1, errno, "can't stat history file");
  1032. if (!(st_buf.st_size))
  1033. error (1, 0, "history file is empty");
  1034. cpstart = xmalloc (2 * STAT_BLOCKSIZE(st_buf));
  1035. cpstart[0] = '\0';
  1036. cp = cpend = cpstart;
  1037. hrec_max = HREC_INCREMENT;
  1038. hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
  1039. hrec_idx = 0;
  1040. for (;;)
  1041. {
  1042. for (nl = cp; nl < cpend && *nl != '\n'; nl++)
  1043. if (!isprint(*nl)) *nl = ' ';
  1044. if (nl >= cpend)
  1045. {
  1046. if (nl - cp >= STAT_BLOCKSIZE(st_buf))
  1047. {
  1048. error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
  1049. (unsigned long) STAT_BLOCKSIZE(st_buf));
  1050. }
  1051. if (nl > cp)
  1052. memmove (cpstart, cp, nl - cp);
  1053. nl = cpstart + (nl - cp);
  1054. cp = cpstart;
  1055. i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
  1056. if (i > 0)
  1057. {
  1058. cpend = nl + i;
  1059. *cpend = '\0';
  1060. continue;
  1061. }
  1062. if (i < 0)
  1063. error (1, errno, "error reading history file");
  1064. if (nl == cp) break;
  1065. error (0, 0, "warning: no newline at end of history file");
  1066. }
  1067. *nl = '\0';
  1068. if (hrec_count == hrec_max)
  1069. {
  1070. struct hrec *old_head = hrec_head;
  1071. hrec_max = xsum (hrec_max, HREC_INCREMENT);
  1072. if (hrec_count == hrec_max
  1073. || size_overflow_p (xtimes (hrec_max, sizeof (struct hrec))))
  1074. error (1, 0, "Too many history records in history file.");
  1075. hrec_head = xrealloc (hrec_head,
  1076. xtimes (hrec_max, sizeof (struct hrec)));
  1077. if (last_since_tag)
  1078. last_since_tag = hrec_head + (last_since_tag - old_head);
  1079. if (last_backto)
  1080. last_backto = hrec_head + (last_backto - old_head);
  1081. }
  1082. /* fill_hrec dates from when history read the entire
  1083. history file in one chunk, and then records were pulled out
  1084. by pointing to the various parts of this big chunk. This is
  1085. why there are ugly hacks here: I don't want to completely
  1086. re-write the whole history stuff right now. */
  1087. hrline = xstrdup ((char *)cp);
  1088. fill_hrec (hrline, &hrec_head[hrec_count]);
  1089. if (select_hrec (&hrec_head[hrec_count]))
  1090. hrec_count++;
  1091. else
  1092. free(hrline);
  1093. cp = nl + 1;
  1094. }
  1095. free (cpstart);
  1096. close (fd);
  1097. /* Special selection problem: If "since_tag" is set, we have saved every
  1098. * record from the 1st occurrence of "since_tag", when we want to save
  1099. * records since the *last* occurrence of "since_tag". So what we have
  1100. * to do is bump hrec_head forward and reduce hrec_count accordingly.
  1101. */
  1102. if (last_since_tag)
  1103. {
  1104. hrec_count -= (last_since_tag - hrec_head);
  1105. hrec_head = last_since_tag;
  1106. }
  1107. /* Much the same thing is necessary for the "backto" option. */
  1108. if (last_backto)
  1109. {
  1110. hrec_count -= (last_backto - hrec_head);
  1111. hrec_head = last_backto;
  1112. }
  1113. }
  1114. /* Utility program for determining whether "find" is inside "string" */
  1115. static int
  1116. within (find, string)
  1117. char *find, *string;
  1118. {
  1119. int c, len;
  1120. if (!find || !string)
  1121. return (0);
  1122. c = *find++;
  1123. len = strlen (find);
  1124. while (*string)
  1125. {
  1126. if (!(string = strchr (string, c)))
  1127. return (0);
  1128. string++;
  1129. if (!strncmp (find, string, len))
  1130. return (1);
  1131. }
  1132. return (0);
  1133. }
  1134. /* The purpose of "select_hrec" is to apply the selection criteria based on
  1135. * the command arguments and defaults and return a flag indicating whether
  1136. * this record should be remembered for printing.
  1137. */
  1138. static int
  1139. select_hrec (hr)
  1140. struct hrec *hr;
  1141. {
  1142. char **cpp, *cp, *cp2;
  1143. struct file_list_str *fl;
  1144. int count;
  1145. /* basic validity checking */
  1146. if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
  1147. !hr->file || !hr->end)
  1148. {
  1149. error (0, 0, "warning: history line %ld invalid", hr->idx);
  1150. return (0);
  1151. }
  1152. /* "Since" checking: The argument parser guarantees that only one of the
  1153. * following four choices is set:
  1154. *
  1155. * 1. If "since_date" is set, it contains the date specified on the
  1156. * command line. hr->date fields earlier than "since_date" are ignored.
  1157. * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
  1158. * number (which is of limited use) or a symbolic TAG. Each RCS file
  1159. * is examined and the date on the specified revision (or the revision
  1160. * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
  1161. * compared against hr->date as in 1. above.
  1162. * 3. If "since_tag" is set, matching tag records are saved. The field
  1163. * "last_since_tag" is set to the last one of these. Since we don't
  1164. * know where the last one will be, all records are saved from the
  1165. * first occurrence of the TAG. Later, at the end of "select_hrec"
  1166. * records before the last occurrence of "since_tag" are skipped.
  1167. * 4. If "backto" is set, all records with a module name or file name
  1168. * matching "backto" are saved. In addition, all records with a
  1169. * repository field with a *prefix* matching "backto" are saved.
  1170. * The field "last_backto" is set to the last one of these. As in
  1171. * 3. above, "select_hrec" adjusts to include the last one later on.
  1172. */
  1173. if (since_date)
  1174. {
  1175. char *ourdate = date_from_time_t (hr->date);
  1176. count = RCS_datecmp (ourdate, since_date);
  1177. free (ourdate);
  1178. if (count < 0)
  1179. return (0);
  1180. }
  1181. else if (*since_rev)
  1182. {
  1183. Vers_TS *vers;
  1184. time_t t;
  1185. struct file_info finfo;
  1186. memset (&finfo, 0, sizeof finfo);
  1187. finfo.file = hr->file;
  1188. /* Not used, so don't worry about it. */
  1189. finfo.update_dir = NULL;
  1190. finfo.fullname = finfo.file;
  1191. finfo.repository = hr->repos;
  1192. finfo.entries = NULL;
  1193. finfo.rcs = NULL;
  1194. vers = Version_TS (&finfo, (char *) NULL, since_rev, (char *) NULL,
  1195. 1, 0);
  1196. if (vers->vn_rcs)
  1197. {
  1198. if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
  1199. != (time_t) 0)
  1200. {
  1201. if (hr->date < t)
  1202. {
  1203. freevers_ts (&vers);
  1204. return (0);
  1205. }
  1206. }
  1207. }
  1208. freevers_ts (&vers);
  1209. }
  1210. else if (*since_tag)
  1211. {
  1212. if (*(hr->type) == 'T')
  1213. {
  1214. /*
  1215. * A 'T'ag record, the "rev" field holds the tag to be set,
  1216. * while the "repos" field holds "D"elete, "A"dd or a rev.
  1217. */
  1218. if (within (since_tag, hr->rev))
  1219. {
  1220. last_since_tag = hr;
  1221. return (1);
  1222. }
  1223. else
  1224. return (0);
  1225. }
  1226. if (!last_since_tag)
  1227. return (0);
  1228. }
  1229. else if (*backto)
  1230. {
  1231. if (within (backto, hr->file) || within (backto, hr->mod) ||
  1232. within (backto, hr->repos))
  1233. last_backto = hr;
  1234. else
  1235. return (0);
  1236. }
  1237. /* User checking:
  1238. *
  1239. * Run down "user_list", match username ("" matches anything)
  1240. * If "" is not there and actual username is not there, return failure.
  1241. */
  1242. if (user_list && hr->user)
  1243. {
  1244. for (cpp = user_list, count = user_count; count; cpp++, count--)
  1245. {
  1246. if (!**cpp)
  1247. break; /* null user == accept */
  1248. if (!strcmp (hr->user, *cpp)) /* found listed user */
  1249. break;
  1250. }
  1251. if (!count)
  1252. return (0); /* Not this user */
  1253. }
  1254. /* Record type checking:
  1255. *
  1256. * 1. If Record type is not in rec_types field, skip it.
  1257. * 2. If mod_list is null, keep everything. Otherwise keep only modules
  1258. * on mod_list.
  1259. * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
  1260. * file_list is null, keep everything. Otherwise, keep only files on
  1261. * file_list, matched appropriately.
  1262. */
  1263. if (!strchr (rec_types, *(hr->type)))
  1264. return (0);
  1265. if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
  1266. {
  1267. if (file_list) /* If file_list is null, accept all */
  1268. {
  1269. for (fl = file_list, count = file_count; count; fl++, count--)
  1270. {
  1271. /* 1. If file_list entry starts with '*', skip the '*' and
  1272. * compare it against the repository in the hrec.
  1273. * 2. If file_list entry has a '/' in it, compare it against
  1274. * the concatenation of the repository and file from hrec.
  1275. * 3. Else compare the file_list entry against the hrec file.
  1276. */
  1277. char *cmpfile = NULL;
  1278. if (*(cp = fl->l_file) == '*')
  1279. {
  1280. cp++;
  1281. /* if argument to -p is a prefix of repository */
  1282. if (!strncmp (cp, hr->repos, strlen (cp)))
  1283. {
  1284. hr->mod = fl->l_module;
  1285. break;
  1286. }
  1287. }
  1288. else
  1289. {
  1290. if (strchr (cp, '/'))
  1291. {
  1292. cmpfile = xmalloc (strlen (hr->repos)
  1293. + strlen (hr->file)
  1294. + 10);
  1295. (void) sprintf (cmpfile, "%s/%s",
  1296. hr->repos, hr->file);
  1297. cp2 = cmpfile;
  1298. }
  1299. else
  1300. {
  1301. cp2 = hr->file;
  1302. }
  1303. /* if requested file is found within {repos}/file fields */
  1304. if (within (cp, cp2))
  1305. {
  1306. hr->mod = fl->l_module;
  1307. if (cmpfile != NULL)
  1308. free (cmpfile);
  1309. break;
  1310. }
  1311. if (cmpfile != NULL)
  1312. free (cmpfile);
  1313. }
  1314. }
  1315. if (!count)
  1316. return (0); /* String specified and no match */
  1317. }
  1318. }
  1319. if (mod_list)
  1320. {
  1321. for (cpp = mod_list, count = mod_count; count; cpp++, count--)
  1322. {
  1323. if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
  1324. break;
  1325. }
  1326. if (!count)
  1327. return (0); /* Module specified & this record is not one of them. */
  1328. }
  1329. return (1); /* Select this record unless rejected above. */
  1330. }
  1331. /* The "sort_order" routine (when handed to qsort) has arranged for the
  1332. * hrecs files to be in the right order for the report.
  1333. *
  1334. * Most of the "selections" are done in the select_hrec routine, but some
  1335. * selections are more easily done after the qsort by "accept_hrec".
  1336. */
  1337. static void
  1338. report_hrecs ()
  1339. {
  1340. struct hrec *hr, *lr;
  1341. struct tm *tm;
  1342. int i, count, ty;
  1343. char *cp;
  1344. int user_len, file_len, rev_len, mod_len, repos_len;
  1345. if (*since_tag && !last_since_tag)
  1346. {
  1347. (void) printf ("No tag found: %s\n", since_tag);
  1348. return;
  1349. }
  1350. else if (*backto && !last_backto)
  1351. {
  1352. (void) printf ("No module, file or repository with: %s\n", backto);
  1353. return;
  1354. }
  1355. else if (hrec_count < 1)
  1356. {
  1357. (void) printf ("No records selected.\n");
  1358. return;
  1359. }
  1360. user_len = file_len = rev_len = mod_len = repos_len = 0;
  1361. /* Run through lists and find maximum field widths */
  1362. hr = lr = hrec_head;
  1363. hr++;
  1364. for (count = hrec_count; count--; lr = hr, hr++)
  1365. {
  1366. char *repos;
  1367. if (!count)
  1368. hr = NULL;
  1369. if (!accept_hrec (lr, hr))
  1370. continue;
  1371. ty = *(lr->type);
  1372. repos = xstrdup (lr->repos);
  1373. if ((cp = strrchr (repos, '/')) != NULL)
  1374. {
  1375. if (lr->mod && !strcmp (++cp, lr->mod))
  1376. {
  1377. (void) strcpy (cp, "*");
  1378. }
  1379. }
  1380. if ((i = strlen (lr->user)) > user_len)
  1381. user_len = i;
  1382. if ((i = strlen (lr->file)) > file_len)
  1383. file_len = i;
  1384. if (ty != 'T' && (i = strlen (repos)) > repos_len)
  1385. repos_len = i;
  1386. if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
  1387. rev_len = i;
  1388. if (lr->mod && (i = strlen (lr->mod)) > mod_len)
  1389. mod_len = i;
  1390. free (repos);
  1391. }
  1392. /* Walk through hrec array setting "lr" (Last Record) to each element.
  1393. * "hr" points to the record following "lr" -- It is NULL in the last
  1394. * pass.
  1395. *
  1396. * There are two sections in the loop below:
  1397. * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
  1398. * decide whether the record should be printed.
  1399. * 2. Based on the record type, format and print the data.
  1400. */
  1401. for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
  1402. {
  1403. char *workdir;
  1404. char *repos;
  1405. if (!hrec_count)
  1406. hr = NULL;
  1407. if (!accept_hrec (lr, hr))
  1408. continue;
  1409. ty = *(lr->type);
  1410. if (!tz_local)
  1411. {
  1412. time_t t = lr->date + tz_seconds_east_of_GMT;
  1413. tm = gmtime (&t);
  1414. }
  1415. else
  1416. tm = localtime (&(lr->date));
  1417. (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
  1418. tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
  1419. tm->tm_min, tz_name, user_len, lr->user);
  1420. workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
  1421. (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
  1422. if ((cp = strrchr (workdir, '/')) != NULL)
  1423. {
  1424. if (lr->mod && !strcmp (++cp, lr->mod))
  1425. {
  1426. (void) strcpy (cp, "*");
  1427. }
  1428. }
  1429. repos = xmalloc (strlen (lr->repos) + 10);
  1430. (void) strcpy (repos, lr->repos);
  1431. if ((cp = strrchr (repos, '/')) != NULL)
  1432. {
  1433. if (lr->mod && !strcmp (++cp, lr->mod))
  1434. {
  1435. (void) strcpy (cp, "*");
  1436. }
  1437. }
  1438. switch (ty)
  1439. {
  1440. case 'T':
  1441. /* 'T'ag records: repository is a "tag type", rev is the tag */
  1442. (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
  1443. repos);
  1444. if (working)
  1445. (void) printf (" {%s}", workdir);
  1446. break;
  1447. case 'F':
  1448. case 'E':
  1449. case 'O':
  1450. if (lr->rev && *(lr->rev))
  1451. (void) printf (" [%s]", lr->rev);
  1452. (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
  1453. mod_len + 1 - (int) strlen (lr->mod),
  1454. "=", workdir);
  1455. break;
  1456. case 'W':
  1457. case 'U':
  1458. case 'P':
  1459. case 'C':
  1460. case 'G':
  1461. case 'M':
  1462. case 'A':
  1463. case 'R':
  1464. (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
  1465. file_len, lr->file, repos_len, repos,
  1466. lr->mod ? lr->mod : "", workdir);
  1467. break;
  1468. default:
  1469. (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
  1470. break;
  1471. }
  1472. (void) putchar ('\n');
  1473. free (workdir);
  1474. free (repos);
  1475. }
  1476. }
  1477. static int
  1478. accept_hrec (lr, hr)
  1479. struct hrec *hr, *lr;
  1480. {
  1481. int ty;
  1482. ty = *(lr->type);
  1483. if (last_since_tag && ty == 'T')
  1484. return (1);
  1485. if (v_checkout)
  1486. {
  1487. if (ty != 'O')
  1488. return (0); /* Only interested in 'O' records */
  1489. /* We want to identify all the states that cause the next record
  1490. * ("hr") to be different from the current one ("lr") and only
  1491. * print a line at the allowed boundaries.
  1492. */
  1493. if (!hr || /* The last record */
  1494. strcmp (hr->user, lr->user) || /* User has changed */
  1495. strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1496. (working && /* If must match "workdir" */
  1497. (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
  1498. strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
  1499. return (1);
  1500. }
  1501. else if (modified)
  1502. {
  1503. if (!last_entry || /* Don't want only last rec */
  1504. !hr || /* Last entry is a "last entry" */
  1505. strcmp (hr->repos, lr->repos) || /* Repository has changed */
  1506. strcmp (hr->file, lr->file))/* File has changed */
  1507. return (1);
  1508. if (working)
  1509. { /* If must match "workdir" */
  1510. if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
  1511. strcmp (hr->end, lr->end)) /* the 2nd parts differ */
  1512. return (1);
  1513. }
  1514. }
  1515. else if (module_report)
  1516. {
  1517. if (!last_entry || /* Don't want only last rec */
  1518. !hr || /* Last entry is a "last entry" */
  1519. strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1520. strcmp (hr->repos, lr->repos) || /* Repository has changed */
  1521. strcmp (hr->file, lr->file))/* File has changed */
  1522. return (1);
  1523. }
  1524. else
  1525. {
  1526. /* "extract" and "tag_report" always print selected records. */
  1527. return (1);
  1528. }
  1529. return (0);
  1530. }