3 * You may distribute under the terms of the GNU General Public License
4 * as specified in the README file that comes with the CVS 1.0 kit.
6 * **************** History of Users and Module ****************
8 * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
10 * On For each Tag, Add, Checkout, Commit, Update or Release command,
11 * one line of text is written to a History log.
13 * X date | user | CurDir | special | rev(s) | argument '\n'
15 * where: [The spaces in the example line above are not in the history file.]
17 * X is a single character showing the type of event:
22 * W "Update" cmd - No User file, Remove from Entries file.
23 * U "Update" cmd - File was checked out over User file.
24 * P "Update" cmd - User file was patched.
25 * G "Update" cmd - File was merged successfully.
26 * C "Update" cmd - File was merged and shows overlaps.
27 * M "Commit" cmd - "Modified" file.
28 * A "Commit" cmd - "Added" file.
29 * R "Commit" cmd - "Removed" file.
31 * date is a fixed length 8-char hex representation of a Unix time_t.
32 * [Starting here, variable fields are delimited by '|' chars.]
34 * user is the username of the person who typed the command.
36 * CurDir The directory where the action occurred. This should be the
37 * absolute path of the directory which is at the same level as
38 * the "Repository" field (for W,U,P,G,C & M,A,R).
40 * Repository For record types [W,U,P,G,C,M,A,R] this field holds the
41 * repository read from the administrative data where the
43 * T "A" --> New Tag, "D" --> Delete Tag
44 * Otherwise it is the Tag or Date to modify.
45 * O,F,E A "" (null field)
47 * rev(s) Revision number or tag.
49 * O,E The Tag or Date, if specified, else "" (null field).
51 * W The Tag or Date, if specified, else "" (null field).
52 * U,P The Revision checked out over the User file.
53 * G,C The Revision(s) involved in merge.
54 * M,A,R RCS Revision affected.
56 * argument The module (for [TOEF]) or file (for [WUPGCMAR]) affected.
59 *** Report categories: "User" and "Since" modifiers apply to all reports.
60 * [For "sort" ordering see the "sort_order" routine.]
62 * Extract list of record types
64 * -e, -x [TOEFWUPGCMAR]
66 * Extracted records are simply printed, No analysis is performed.
67 * All "field" modifiers apply. -e chooses all types.
69 * Checked 'O'ut modules
72 * Checked out modules. 'F' and 'O' records are examined and if
73 * the last record for a repository/file is an 'O', a line is
74 * printed. "-w" forces the "working dir" to be used in the
75 * comparison instead of the repository.
77 * Committed (Modified) files
80 * All 'M'odified, 'A'dded and 'R'emoved records are examined.
81 * "Field" modifiers apply. -l forces a sort by file within user
82 * and shows only the last modifier. -w works as in Checkout.
84 * Warning: Be careful with what you infer from the output of
85 * "cvs hi -c -l". It means the last time *you*
86 * changed the file, not the list of files for which
87 * you were the last changer!!!
89 * Module history for named modules.
92 * This is special. If one or more modules are specified, the
93 * module names are remembered and the files making up the
94 * modules are remembered. Only records matching exactly those
95 * files and repositories are shown. Sorting by "module", then
96 * filename, is implied. If -l ("last modified") is specified,
97 * then "update" records (types WUPCG), tag and release records
98 * are ignored and the last (by date) "modified" record.
102 * -T All Tag records are displayed.
106 * Since ... [All records contain a timestamp, so any report
107 * category can be limited by date.]
109 * -D date - The "date" is parsed into a Unix "time_t" and
110 * records with an earlier time stamp are ignored.
111 * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
112 * you use this option, every file is searched for the
114 * -t tag - The "tag" is searched for in the history file and no
115 * record is displayed before the tag is found. An
116 * error is printed if the tag is never found.
117 * -b string - Records are printed only back to the last reference
118 * to the string in the "module", "file" or
119 * "repository" fields.
121 * Field Selections [Simple comparisons on existing fields. All field
122 * selections are repeatable.]
125 * -u user - If no user is given and '-a' is not given, only
126 * records for the user typing the command are shown.
127 * ==> If -a or -u is not specified, just use "self".
129 * -f filematch - Only records in which the "file" field contains the
130 * string "filematch" are considered.
132 * -p repository - Only records in which the "repository" string is a
133 * prefix of the "repos" field are considered.
135 * -n modulename - Only records which contain "modulename" in the
136 * "module" field are considered.
139 * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
141 *** Checked out files for username. (default self, e.g. "dgg")
142 * cvs hi [equivalent to: "cvs hi -o -u dgg"]
143 * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
144 * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
146 *** Committed (modified) files from the beginning of the file.
147 * cvs hi -c [-u user]
149 *** Committed (modified) files since Midnight, January 1, 1990:
150 * cvs hi -c -D 'Jan 1 1990' [-u user]
152 *** Committed (modified) files since tag "TAG" was stored in the history file:
153 * cvs hi -c -t TAG [-u user]
155 *** Committed (modified) files since tag "TAG" was placed on the files:
156 * cvs hi -c -r TAG [-u user]
158 *** Who last committed file/repository X?
159 * cvs hi -c -l -[fp] X
161 *** Modified files since tag/date/file/repos?
162 * cvs hi -c {-r TAG | -D Date | -b string}
167 *** History of file/repository/module X.
170 *** History of user "user".
173 *** Dump (eXtract) specified record types
174 * cvs hi -x [TOEFWUPGCMAR]
177 * FUTURE: J[Join], I[Import] (Not currently implemented.)
183 #include "save-cwd.h"
187 char *type; /* Type of record (In history record) */
188 char *user; /* Username (In history record) */
189 char *dir; /* "Compressed" Working dir (In history record) */
190 char *repos; /* (Tag is special.) Repository (In history record) */
191 char *rev; /* Revision affected (In history record) */
192 char *file; /* Filename (In history record) */
193 char *end; /* Ptr into repository to copy at end of workdir */
194 char *mod; /* The module within which the file is contained */
195 time_t date; /* Calculated from date stored in record */
196 long idx; /* Index of record, for "stable" sort. */
198 static long hrec_idx;
201 static void fill_hrec (char *line, struct hrec * hr);
202 static int accept_hrec (struct hrec * hr, struct hrec * lr);
203 static int select_hrec (struct hrec * hr);
204 static int sort_order (const void *l, const void *r);
205 static int within (char *find, char *string);
206 static void expand_modules (void);
207 static void read_hrecs (const char *fname);
208 static void report_hrecs (void);
209 static void save_file (char *dir, char *name, char *module);
210 static void save_module (char *module);
211 static void save_user (char *name);
213 #define USER_INCREMENT 2
214 #define FILE_INCREMENT 128
215 #define MODULE_INCREMENT 5
216 #define HREC_INCREMENT 128
218 static short report_count;
220 static short extract;
221 static short extract_all;
222 static short v_checkout;
223 static short modified;
224 static short tag_report;
225 static short module_report;
226 static short working;
227 static short last_entry;
228 static short all_users;
230 static short user_sort;
231 static short repos_sort;
232 static short file_sort;
233 static short module_sort;
235 static short tz_local;
236 static time_t tz_seconds_east_of_GMT;
237 static char *tz_name = "+0000";
239 /* -r, -t, or -b options, malloc'd. These are "" if the option in
240 question is not specified or is overridden by another option. The
241 main reason for using "" rather than NULL is historical. Together
242 with since_date, these are a mutually exclusive set; one overrides the
244 static char *since_rev;
245 static char *since_tag;
247 /* -D option, or 0 if not specified. RCS format. */
248 static char * since_date;
250 static struct hrec *last_since_tag;
251 static struct hrec *last_backto;
253 /* Record types to look for, malloc'd. Probably could be statically
254 allocated, but only if we wanted to check for duplicates more than
256 static char *rec_types;
258 static int hrec_count;
261 static char **user_list; /* Ptr to array of ptrs to user names */
262 static int user_max; /* Number of elements allocated */
263 static int user_count; /* Number of elements used */
265 static struct file_list_str
269 } *file_list; /* Ptr to array file name structs */
270 static int file_max; /* Number of elements allocated */
271 static int file_count; /* Number of elements used */
273 static char **mod_list; /* Ptr to array of ptrs to module names */
274 static int mod_max; /* Number of elements allocated */
275 static int mod_count; /* Number of elements used */
277 static char *histfile; /* Ptr to the history file name */
279 /* This is pretty unclear. First of all, separating "flags" vs.
280 "options" (I think the distinction is that "options" take arguments)
281 is nonstandard, and not something we do elsewhere in CVS. Second of
282 all, what does "reports" mean? I think it means that you can only
283 supply one of those options, but "reports" hardly has that meaning in
284 a self-explanatory way. */
285 static const char *const history_usg[] =
287 "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
289 " -T Produce report on all TAGs\n",
290 " -c Committed (Modified) files\n",
291 " -o Checked out modules\n",
292 " -m <module> Look for specified module (repeatable)\n",
293 " -x [" ALL_HISTORY_REC_TYPES "] Extract by record type\n",
294 " -e Everything (same as -x, but all record types)\n",
296 " -a All users (Default is self)\n",
297 " -l Last modified (committed or modified report)\n",
298 " -w Working directory must match\n",
300 " -D <date> Since date (Many formats)\n",
301 " -b <str> Back to record with str in module/file/repos field\n",
302 " -f <file> Specified file (same as command line) (repeatable)\n",
303 " -n <modulename> In module (repeatable)\n",
304 " -p <repos> In repository (repeatable)\n",
305 " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
306 " -t <tag> Since tag record placed in history file (by anyone).\n",
307 " -u <user> For user name (repeatable)\n",
308 " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
311 /* Sort routine for qsort:
312 - If a user is selected at all, sort it first. User-within-file is useless.
313 - If a module was selected explicitly, sort next on module.
314 - Then sort by file. "File" is "repository/file" unless "working" is set,
315 then it is "workdir/file". (Revision order should always track date.)
316 - Always sort timestamp last.
319 sort_order (const void *l, const void *r)
322 const struct hrec *left = l;
323 const struct hrec *right = r;
325 if (user_sort) /* If Sort by username, compare users */
327 if ((i = strcmp (left->user, right->user)) != 0)
330 if (module_sort) /* If sort by modules, compare module names */
332 if (left->mod && right->mod)
333 if ((i = strcmp (left->mod, right->mod)) != 0)
336 if (repos_sort) /* If sort by repository, compare them. */
338 if ((i = strcmp (left->repos, right->repos)) != 0)
341 if (file_sort) /* If sort by filename, compare files, NOT dirs. */
343 if ((i = strcmp (left->file, right->file)) != 0)
348 if ((i = strcmp (left->dir, right->dir)) != 0)
351 if ((i = strcmp (left->end, right->end)) != 0)
357 * By default, sort by date, time
358 * XXX: This fails after 2030 when date slides into sign bit
360 if ((i = ((long) (left->date) - (long) (right->date))) != 0)
363 /* For matching dates, keep the sort stable by using record index */
364 return left->idx - right->idx;
368 history (int argc, char **argv)
376 since_rev = xstrdup ("");
377 since_tag = xstrdup ("");
378 backto = xstrdup ("");
379 rec_types = xstrdup ("");
381 while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
385 case 'T': /* Tag list */
389 case 'a': /* For all usernames */
400 rec_types = xstrdup (ALL_HISTORY_REC_TYPES);
402 case 'l': /* Find Last file record */
409 case 'w': /* Match Working Dir (CurDir) fields */
412 case 'X': /* Undocumented debugging flag */
418 case 'D': /* Since specified date */
419 if (*since_rev || *since_tag || *backto)
421 error (0, 0, "date overriding rev/tag/backto");
422 *since_rev = *since_tag = *backto = '\0';
424 since_date = Make_Date (optarg);
426 case 'b': /* Since specified file/Repos */
427 if (since_date || *since_rev || *since_tag)
429 error (0, 0, "backto overriding date/rev/tag");
430 *since_rev = *since_tag = '\0';
431 if (since_date != NULL)
436 backto = xstrdup (optarg);
438 case 'f': /* For specified file */
439 save_file (NULL, optarg, NULL);
441 case 'm': /* Full module report */
442 if (!module_report++) report_count++;
444 case 'n': /* Look for specified module */
445 save_module (optarg);
447 case 'p': /* For specified directory */
448 save_file (optarg, NULL, NULL);
450 case 'r': /* Since specified Tag/Rev */
451 if (since_date || *since_tag || *backto)
453 error (0, 0, "rev overriding date/tag/backto");
454 *since_tag = *backto = '\0';
455 if (since_date != NULL)
460 since_rev = xstrdup (optarg);
462 case 't': /* Since specified Tag/Rev */
463 if (since_date || *since_rev || *backto)
465 error (0, 0, "tag overriding date/marker/file/repos");
466 *since_rev = *backto = '\0';
467 if (since_date != NULL)
472 since_tag = xstrdup (optarg);
474 case 'u': /* For specified username */
483 for (cp = optarg; *cp; cp++)
484 if (!strchr (ALL_HISTORY_REC_TYPES, *cp))
485 error (1, 0, "%c is not a valid report type", *cp);
488 rec_types = xstrdup (optarg);
492 (optarg[0] == 'l' || optarg[0] == 'L')
493 && (optarg[1] == 't' || optarg[1] == 'T')
500 * Convert a known time with the given timezone to time_t.
501 * Use the epoch + 23 hours, so timezones east of GMT work.
504 char *buf = Xasprintf ("1/1/1970 23:00 %s", optarg);
505 if (get_date (&t, buf, NULL))
508 * Convert to seconds east of GMT, removing the
509 * 23-hour offset mentioned above.
511 tz_seconds_east_of_GMT = (time_t)23 * 60 * 60
516 error (0, 0, "%s is not a known time zone", optarg);
528 for (i = 0; i < argc; i++)
529 save_file (NULL, argv[i], NULL);
532 /* ================ Now analyze the arguments a bit */
535 else if (report_count > 1)
536 error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
538 #ifdef CLIENT_SUPPORT
539 if (current_parsed_root->isremote)
541 struct file_list_str *f1;
544 /* We're the client side. Fire up the remote server. */
564 client_senddate (since_date);
565 if (backto[0] != '\0')
566 option_with_arg ("-b", backto);
567 for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
569 if (f1->l_file[0] == '*')
570 option_with_arg ("-p", f1->l_file + 1);
572 option_with_arg ("-f", f1->l_file);
576 for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
577 option_with_arg ("-n", *mod);
579 option_with_arg ("-r", since_rev);
581 option_with_arg ("-t", since_tag);
582 for (mod = user_list; mod < &user_list[user_count]; ++mod)
583 option_with_arg ("-u", *mod);
587 option_with_arg ("-x", rec_types);
588 option_with_arg ("-z", tz_name);
590 send_to_server ("history\012", 0);
591 return get_responses_and_close ();
603 if (!strchr (rec_types, 'T'))
605 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
606 (void) strcat (rec_types, "T");
609 else if (extract || extract_all)
617 rec_types = xstrdup ("MAR");
619 * If the user has not specified a date oriented flag ("Since"), sort
620 * by Repository/file before date. Default is "just" date.
623 || (!since_date && !*since_rev && !*since_tag && !*backto))
628 * If we are not looking for last_modified and the user specified
629 * one or more users to look at, sort by user before filename.
631 if (!last_entry && user_list)
635 else if (module_report)
638 rec_types = xstrdup (last_entry ? "OMAR" : ALL_HISTORY_REC_TYPES);
642 working = 0; /* User's workdir doesn't count here */
645 /* Must be "checkout" or default */
648 rec_types = xstrdup ("OF");
649 /* See comments in "modified" above */
650 if (!last_entry && user_list)
653 || (!since_date && !*since_rev && !*since_tag && !*backto))
657 /* If no users were specified, use self (-a saves a universal ("") user) */
659 save_user (getcaller ());
661 /* If we're looking back to a Tag value, must consider "Tag" records */
662 if (*since_tag && !strchr (rec_types, 'T'))
664 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
665 (void) strcat (rec_types, "T");
669 fname = xstrdup (histfile);
671 fname = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
672 CVSROOTADM, CVSROOTADM_HISTORY);
677 qsort (hrec_head, hrec_count, sizeof (struct hrec), sort_order);
681 if (since_date != NULL)
694 history_write (int type, const char *update_dir, const char *revs,
695 const char *name, const char *repository)
699 char *username = getcaller ();
702 char *slash = "", *cp;
703 const char *cp2, *repos;
705 static char *tilde = "";
706 static char *PrCurDir = NULL;
708 if (logoff) /* History is turned off by noexec or
712 if (!strchr (config->logHistory, type))
715 fname = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
716 CVSROOTADM, CVSROOTADM_HISTORY);
718 /* turn off history logging if the history file does not exist */
719 /* FIXME: This should check for write permissions instead. This way,
720 * O_CREATE could be added back into the call to open() below and
721 * there would be no race condition involved in log rotation.
723 * Note that the new method of turning off logging would be either via
724 * the CVSROOT/config file (probably the quicker method, but would need
725 * to be added, or at least checked for, too) or by creating a dummy
726 * history file with 0444 permissions.
734 TRACE (TRACE_FUNCTION, "fopen(%s,a)", fname);
738 fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | OPEN_BINARY, 0666);
743 error (0, errno, "warning: cannot write to history file %s",
749 repos = Short_Repository (repository);
755 pwdir = get_homedir ();
759 /* Assumes neither CurDir nor pwdir ends in '/' */
761 if (!strncmp (CurDir, pwdir, i))
763 PrCurDir += i; /* Point to '/' separator */
768 /* Try harder to find a "homedir" */
769 struct saved_cwd cwd;
773 error (1, errno, "Failed to save current directory.");
775 if (CVS_CHDIR (pwdir) < 0 || (homedir = xgetcwd ()) == NULL)
778 if (restore_cwd (&cwd))
780 "Failed to restore current directory, `%s'.",
784 i = strlen (homedir);
785 if (!strncmp (CurDir, homedir, i))
787 PrCurDir += i; /* Point to '/' separator */
791 if (homedir != pwdir)
802 else if (update_dir && *update_dir)
807 workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash)
808 + strlen (update_dir) + 10);
809 (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
812 * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
813 * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
815 * "$workdir/$name" is the working file name.
816 * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
818 * First, note that the history format was intended to save space, not
819 * to be human readable.
821 * The working file directory ("workdir") and the Repository ("repos")
822 * usually end with the same one or more directory elements. To avoid
823 * duplication (and save space), the "workdir" field ends with
824 * an integer offset into the "repos" field. This offset indicates the
825 * beginning of the "tail" of "repos", after which all characters are
828 * In other words, if the "workdir" field has a '*' (a very stupid thing
829 * to put in a filename) in it, then every thing following the last '*'
830 * is a hex offset into "repos" of the first character from "repos" to
831 * append to "workdir" to finish the pathname.
833 * It might be easier to look at an example:
835 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
837 * Indicates that the workdir is really "~/work/cvs/examples", saving
838 * 10 characters, where "~/work*d" would save 6 characters and mean that
839 * the workdir is really "~/work/examples". It will mean more on
840 * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
842 * "workdir" is always an absolute pathname (~/xxx is an absolute path)
843 * "repos" is always a relative pathname. So we can assume that we will
844 * never run into the top of "workdir" -- there will always be a '/' or
845 * a '~' at the head of "workdir" that is not matched by anything in
846 * "repos". On the other hand, we *can* run off the top of "repos".
848 * Only "compress" if we save characters.
851 cp = workdir + strlen (workdir) - 1;
852 cp2 = repos + strlen (repos) - 1;
853 for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
858 i = strlen (repos) - i;
859 (void) sprintf ((cp + 1), "*%x", i);
864 line = Xasprintf ("%c%08lx|%s|%s|%s|%s|%s\n", type, (long) time (NULL),
865 username, workdir, repos, revs, name);
867 /* Lessen some race conditions on non-Posix-compliant hosts. */
868 if (lseek (fd, (off_t) 0, SEEK_END) == -1)
869 error (1, errno, "cannot seek to end of history file: %s", fname);
871 if (write (fd, line, strlen (line)) < 0)
872 error (1, errno, "cannot write to history file: %s", fname);
875 error (1, errno, "cannot close history file: %s", fname);
882 * save_user() adds a user name to the user list to select. Zero-length
883 * username ("") matches any user.
886 save_user (char *name)
888 if (user_count == user_max)
890 user_max = xsum (user_max, USER_INCREMENT);
891 if (size_overflow_p (xtimes (user_max, sizeof (char *))))
893 error (0, 0, "save_user: too many users");
896 user_list = xnrealloc (user_list, user_max, sizeof (char *));
898 user_list[user_count++] = xstrdup (name);
902 * save_file() adds file name and associated module to the file list to select.
904 * If "dir" is null, store a file name as is.
905 * If "name" is null, store a directory name with a '*' on the front.
906 * Else, store concatenated "dir/name".
908 * Later, in the "select" stage:
909 * - if it starts with '*', it is prefix-matched against the repository.
910 * - if it has a '/' in it, it is matched against the repository/file.
911 * - else it is matched against the file name.
914 save_file (char *dir, char *name, char *module)
916 struct file_list_str *fl;
918 if (file_count == file_max)
920 file_max = xsum (file_max, FILE_INCREMENT);
921 if (size_overflow_p (xtimes (file_max, sizeof (*fl))))
923 error (0, 0, "save_file: too many files");
926 file_list = xnrealloc (file_list, file_max, sizeof (*fl));
928 fl = &file_list[file_count++];
929 fl->l_module = module;
934 fl->l_file = Xasprintf ("%s/%s", dir, name);
936 fl->l_file = Xasprintf ("*%s", dir);
941 fl->l_file = xstrdup (name);
943 error (0, 0, "save_file: null dir and file name");
948 save_module (char *module)
950 if (mod_count == mod_max)
952 mod_max = xsum (mod_max, MODULE_INCREMENT);
953 if (size_overflow_p (xtimes (mod_max, sizeof (char *))))
955 error (0, 0, "save_module: too many modules");
958 mod_list = xnrealloc (mod_list, mod_max, sizeof (char *));
960 mod_list[mod_count++] = xstrdup (module);
964 expand_modules (void)
970 * Take a ptr to 7-part history line, ending with a newline, for example:
972 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
974 * Split it into 7 parts and drop the parts into a "struct hrec".
975 * Return a pointer to the character following the newline.
979 #define NEXT_BAR(here) do { \
980 while (isspace (*line)) line++; \
982 while ((c = *line++) && c != '|') ; \
983 if (!c) return; line[-1] = '\0'; \
987 fill_hrec (char *line, struct hrec *hr)
992 hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
993 hr->end = hr->mod = NULL;
995 hr->idx = ++hrec_idx;
997 while (isspace ((unsigned char) *line))
1001 hr->date = strtoul (line, &cp, 16);
1002 if (cp == line || *cp != '|')
1007 if ((cp = strrchr (hr->dir, '*')) != NULL)
1010 hr->end = line + strtoul (cp, NULL, 16);
1013 hr->end = line - 1; /* A handy pointer to '\0' */
1016 if (strchr ("FOET", *(hr->type)))
1023 #ifndef STAT_BLOCKSIZE
1024 #if HAVE_STRUCT_STAT_ST_BLKSIZE
1025 #define STAT_BLOCKSIZE(s) (s).st_blksize
1027 #define STAT_BLOCKSIZE(s) (4 * 1024)
1032 /* read_hrecs's job is to read the history file and fill in all the "hrec"
1033 * (history record) array elements with the ones we need to print.
1036 * - Read a block from the file.
1037 * - Walk through the block parsing line into hr records.
1038 * - if the hr isn't used, free its strings, if it is, bump the hrec counter
1039 * - at the end of a block, copy the end of the current block to the start
1040 * of space for the next block, then read in the next block. If we get less
1041 * than the whole block, we're done.
1044 read_hrecs (const char *fname)
1046 unsigned char *cpstart, *cpend, *cp, *nl;
1052 if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
1053 error (1, errno, "cannot open history file: %s", fname);
1055 if (fstat (fd, &st_buf) < 0)
1056 error (1, errno, "can't stat history file");
1058 if (!(st_buf.st_size))
1059 error (1, 0, "history file is empty");
1061 cpstart = xnmalloc (2, STAT_BLOCKSIZE (st_buf));
1063 cp = cpend = cpstart;
1065 hrec_max = HREC_INCREMENT;
1066 hrec_head = xnmalloc (hrec_max, sizeof (struct hrec));
1071 for (nl = cp; nl < cpend && *nl != '\n'; nl++)
1072 if (!isprint (*nl)) *nl = ' ';
1076 if (nl - cp >= STAT_BLOCKSIZE (st_buf))
1078 error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
1079 (unsigned long) STAT_BLOCKSIZE(st_buf));
1082 memmove (cpstart, cp, nl - cp);
1083 nl = cpstart + (nl - cp);
1085 i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
1093 error (1, errno, "error reading history file");
1094 if (nl == cp) break;
1095 error (0, 0, "warning: no newline at end of history file");
1099 if (hrec_count == hrec_max)
1101 struct hrec *old_head = hrec_head;
1103 hrec_max += HREC_INCREMENT;
1104 hrec_head = xnrealloc (hrec_head, hrec_max, sizeof (struct hrec));
1106 last_since_tag = hrec_head + (last_since_tag - old_head);
1108 last_backto = hrec_head + (last_backto - old_head);
1111 /* fill_hrec dates from when history read the entire
1112 history file in one chunk, and then records were pulled out
1113 by pointing to the various parts of this big chunk. This is
1114 why there are ugly hacks here: I don't want to completely
1115 re-write the whole history stuff right now. */
1117 hrline = xstrdup (cp);
1118 fill_hrec (hrline, &hrec_head[hrec_count]);
1119 if (select_hrec (&hrec_head[hrec_count]))
1129 /* Special selection problem: If "since_tag" is set, we have saved every
1130 * record from the 1st occurrence of "since_tag", when we want to save
1131 * records since the *last* occurrence of "since_tag". So what we have
1132 * to do is bump hrec_head forward and reduce hrec_count accordingly.
1136 hrec_count -= (last_since_tag - hrec_head);
1137 hrec_head = last_since_tag;
1140 /* Much the same thing is necessary for the "backto" option. */
1143 hrec_count -= (last_backto - hrec_head);
1144 hrec_head = last_backto;
1148 /* Utility program for determining whether "find" is inside "string" */
1150 within (char *find, char *string)
1154 if (!find || !string)
1158 len = strlen (find);
1162 if (!(string = strchr (string, c)))
1165 if (!strncmp (find, string, len))
1171 /* The purpose of "select_hrec" is to apply the selection criteria based on
1172 * the command arguments and defaults and return a flag indicating whether
1173 * this record should be remembered for printing.
1176 select_hrec (struct hrec *hr)
1178 char **cpp, *cp, *cp2;
1179 struct file_list_str *fl;
1182 /* basic validity checking */
1183 if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
1184 !hr->file || !hr->end)
1186 error (0, 0, "warning: history line %ld invalid", hr->idx);
1190 /* "Since" checking: The argument parser guarantees that only one of the
1191 * following four choices is set:
1193 * 1. If "since_date" is set, it contains the date specified on the
1194 * command line. hr->date fields earlier than "since_date" are ignored.
1195 * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
1196 * number (which is of limited use) or a symbolic TAG. Each RCS file
1197 * is examined and the date on the specified revision (or the revision
1198 * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
1199 * compared against hr->date as in 1. above.
1200 * 3. If "since_tag" is set, matching tag records are saved. The field
1201 * "last_since_tag" is set to the last one of these. Since we don't
1202 * know where the last one will be, all records are saved from the
1203 * first occurrence of the TAG. Later, at the end of "select_hrec"
1204 * records before the last occurrence of "since_tag" are skipped.
1205 * 4. If "backto" is set, all records with a module name or file name
1206 * matching "backto" are saved. In addition, all records with a
1207 * repository field with a *prefix* matching "backto" are saved.
1208 * The field "last_backto" is set to the last one of these. As in
1209 * 3. above, "select_hrec" adjusts to include the last one later on.
1213 char *ourdate = date_from_time_t (hr->date);
1214 count = RCS_datecmp (ourdate, since_date);
1219 else if (*since_rev)
1223 struct file_info finfo;
1225 memset (&finfo, 0, sizeof finfo);
1226 finfo.file = hr->file;
1227 /* Not used, so don't worry about it. */
1228 finfo.update_dir = NULL;
1229 finfo.fullname = finfo.file;
1230 finfo.repository = hr->repos;
1231 finfo.entries = NULL;
1234 vers = Version_TS (&finfo, NULL, since_rev, NULL, 1, 0);
1237 if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, NULL, 0))
1242 freevers_ts (&vers);
1247 freevers_ts (&vers);
1249 else if (*since_tag)
1251 if (*(hr->type) == 'T')
1254 * A 'T'ag record, the "rev" field holds the tag to be set,
1255 * while the "repos" field holds "D"elete, "A"dd or a rev.
1257 if (within (since_tag, hr->rev))
1259 last_since_tag = hr;
1265 if (!last_since_tag)
1270 if (within (backto, hr->file) || within (backto, hr->mod) ||
1271 within (backto, hr->repos))
1279 * Run down "user_list", match username ("" matches anything)
1280 * If "" is not there and actual username is not there, return failure.
1282 if (user_list && hr->user)
1284 for (cpp = user_list, count = user_count; count; cpp++, count--)
1287 break; /* null user == accept */
1288 if (!strcmp (hr->user, *cpp)) /* found listed user */
1292 return 0; /* Not this user */
1295 /* Record type checking:
1297 * 1. If Record type is not in rec_types field, skip it.
1298 * 2. If mod_list is null, keep everything. Otherwise keep only modules
1300 * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
1301 * file_list is null, keep everything. Otherwise, keep only files on
1302 * file_list, matched appropriately.
1304 if (!strchr (rec_types, *(hr->type)))
1306 if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
1308 if (file_list) /* If file_list is null, accept all */
1310 for (fl = file_list, count = file_count; count; fl++, count--)
1312 /* 1. If file_list entry starts with '*', skip the '*' and
1313 * compare it against the repository in the hrec.
1314 * 2. If file_list entry has a '/' in it, compare it against
1315 * the concatenation of the repository and file from hrec.
1316 * 3. Else compare the file_list entry against the hrec file.
1318 char *cmpfile = NULL;
1320 if (*(cp = fl->l_file) == '*')
1323 /* if argument to -p is a prefix of repository */
1324 if (!strncmp (cp, hr->repos, strlen (cp)))
1326 hr->mod = fl->l_module;
1332 if (strchr (cp, '/'))
1334 cmpfile = Xasprintf ("%s/%s", hr->repos, hr->file);
1342 /* if requested file is found within {repos}/file fields */
1343 if (within (cp, cp2))
1345 hr->mod = fl->l_module;
1346 if (cmpfile != NULL)
1350 if (cmpfile != NULL)
1355 return 0; /* String specified and no match */
1360 for (cpp = mod_list, count = mod_count; count; cpp++, count--)
1362 if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
1366 return 0; /* Module specified & this record is not one of them. */
1369 return 1; /* Select this record unless rejected above. */
1372 /* The "sort_order" routine (when handed to qsort) has arranged for the
1373 * hrecs files to be in the right order for the report.
1375 * Most of the "selections" are done in the select_hrec routine, but some
1376 * selections are more easily done after the qsort by "accept_hrec".
1381 struct hrec *hr, *lr;
1385 int user_len, file_len, rev_len, mod_len, repos_len;
1387 if (*since_tag && !last_since_tag)
1389 (void) printf ("No tag found: %s\n", since_tag);
1392 else if (*backto && !last_backto)
1394 (void) printf ("No module, file or repository with: %s\n", backto);
1397 else if (hrec_count < 1)
1399 (void) printf ("No records selected.\n");
1403 user_len = file_len = rev_len = mod_len = repos_len = 0;
1405 /* Run through lists and find maximum field widths */
1406 hr = lr = hrec_head;
1408 for (count = hrec_count; count--; lr = hr, hr++)
1414 if (!accept_hrec (lr, hr))
1418 repos = xstrdup (lr->repos);
1419 if ((cp = strrchr (repos, '/')) != NULL)
1421 if (lr->mod && !strcmp (++cp, lr->mod))
1423 (void) strcpy (cp, "*");
1426 if ((i = strlen (lr->user)) > user_len)
1428 if ((i = strlen (lr->file)) > file_len)
1430 if (ty != 'T' && (i = strlen (repos)) > repos_len)
1432 if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
1434 if (lr->mod && (i = strlen (lr->mod)) > mod_len)
1439 /* Walk through hrec array setting "lr" (Last Record) to each element.
1440 * "hr" points to the record following "lr" -- It is NULL in the last
1443 * There are two sections in the loop below:
1444 * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
1445 * decide whether the record should be printed.
1446 * 2. Based on the record type, format and print the data.
1448 for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
1455 if (!accept_hrec (lr, hr))
1461 time_t t = lr->date + tz_seconds_east_of_GMT;
1465 tm = localtime (&(lr->date));
1467 (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
1468 tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
1469 tm->tm_min, tz_name, user_len, lr->user);
1471 workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
1472 (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
1473 if ((cp = strrchr (workdir, '/')) != NULL)
1475 if (lr->mod && !strcmp (++cp, lr->mod))
1477 (void) strcpy (cp, "*");
1480 repos = xmalloc (strlen (lr->repos) + 10);
1481 (void) strcpy (repos, lr->repos);
1482 if ((cp = strrchr (repos, '/')) != NULL)
1484 if (lr->mod && !strcmp (++cp, lr->mod))
1486 (void) strcpy (cp, "*");
1493 /* 'T'ag records: repository is a "tag type", rev is the tag */
1494 (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
1497 (void) printf (" {%s}", workdir);
1502 if (lr->rev && *(lr->rev))
1503 (void) printf (" [%s]", lr->rev);
1504 (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
1505 mod_len + 1 - (int) strlen (lr->mod),
1516 (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
1517 file_len, lr->file, repos_len, repos,
1518 lr->mod ? lr->mod : "", workdir);
1521 (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
1524 (void) putchar ('\n');
1531 accept_hrec (struct hrec *lr, struct hrec *hr)
1537 if (last_since_tag && ty == 'T')
1543 return 0; /* Only interested in 'O' records */
1545 /* We want to identify all the states that cause the next record
1546 * ("hr") to be different from the current one ("lr") and only
1547 * print a line at the allowed boundaries.
1550 if (!hr || /* The last record */
1551 strcmp (hr->user, lr->user) || /* User has changed */
1552 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1553 (working && /* If must match "workdir" */
1554 (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1555 strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
1561 if (!last_entry || /* Don't want only last rec */
1562 !hr || /* Last entry is a "last entry" */
1563 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1564 strcmp (hr->file, lr->file))/* File has changed */
1568 { /* If must match "workdir" */
1569 if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1570 strcmp (hr->end, lr->end)) /* the 2nd parts differ */
1574 else if (module_report)
1576 if (!last_entry || /* Don't want only last rec */
1577 !hr || /* Last entry is a "last entry" */
1578 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1579 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1580 strcmp (hr->file, lr->file))/* File has changed */
1585 /* "extract" and "tag_report" always print selected records. */