2 * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
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)
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.
15 /* **************** History of Users and Module ****************
17 * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
19 * On For each Tag, Add, Checkout, Commit, Update or Release command,
20 * one line of text is written to a History log.
22 * X date | user | CurDir | special | rev(s) | argument '\n'
24 * where: [The spaces in the example line above are not in the history file.]
26 * X is a single character showing the type of event:
31 * W "Update" cmd - No User file, Remove from Entries file.
32 * U "Update" cmd - File was checked out over User file.
33 * P "Update" cmd - User file was patched.
34 * G "Update" cmd - File was merged successfully.
35 * C "Update" cmd - File was merged and shows overlaps.
36 * M "Commit" cmd - "Modified" file.
37 * A "Commit" cmd - "Added" file.
38 * R "Commit" cmd - "Removed" file.
40 * date is a fixed length 8-char hex representation of a Unix time_t.
41 * [Starting here, variable fields are delimited by '|' chars.]
43 * user is the username of the person who typed the command.
45 * CurDir The directory where the action occurred. This should be the
46 * absolute path of the directory which is at the same level as
47 * the "Repository" field (for W,U,P,G,C & M,A,R).
49 * Repository For record types [W,U,P,G,C,M,A,R] this field holds the
50 * repository read from the administrative data where the
52 * T "A" --> New Tag, "D" --> Delete Tag
53 * Otherwise it is the Tag or Date to modify.
54 * O,F,E A "" (null field)
56 * rev(s) Revision number or tag.
58 * O,E The Tag or Date, if specified, else "" (null field).
60 * W The Tag or Date, if specified, else "" (null field).
61 * U,P The Revision checked out over the User file.
62 * G,C The Revision(s) involved in merge.
63 * M,A,R RCS Revision affected.
65 * argument The module (for [TOEF]) or file (for [WUPGCMAR]) affected.
68 *** Report categories: "User" and "Since" modifiers apply to all reports.
69 * [For "sort" ordering see the "sort_order" routine.]
71 * Extract list of record types
73 * -e, -x [TOEFWUPGCMAR]
75 * Extracted records are simply printed, No analysis is performed.
76 * All "field" modifiers apply. -e chooses all types.
78 * Checked 'O'ut modules
81 * Checked out modules. 'F' and 'O' records are examined and if
82 * the last record for a repository/file is an 'O', a line is
83 * printed. "-w" forces the "working dir" to be used in the
84 * comparison instead of the repository.
86 * Committed (Modified) files
89 * All 'M'odified, 'A'dded and 'R'emoved records are examined.
90 * "Field" modifiers apply. -l forces a sort by file within user
91 * and shows only the last modifier. -w works as in Checkout.
93 * Warning: Be careful with what you infer from the output of
94 * "cvs hi -c -l". It means the last time *you*
95 * changed the file, not the list of files for which
96 * you were the last changer!!!
98 * Module history for named modules.
101 * This is special. If one or more modules are specified, the
102 * module names are remembered and the files making up the
103 * modules are remembered. Only records matching exactly those
104 * files and repositories are shown. Sorting by "module", then
105 * filename, is implied. If -l ("last modified") is specified,
106 * then "update" records (types WUPCG), tag and release records
107 * are ignored and the last (by date) "modified" record.
111 * -T All Tag records are displayed.
115 * Since ... [All records contain a timestamp, so any report
116 * category can be limited by date.]
118 * -D date - The "date" is parsed into a Unix "time_t" and
119 * records with an earlier time stamp are ignored.
120 * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
121 * you use this option, every file is searched for the
123 * -t tag - The "tag" is searched for in the history file and no
124 * record is displayed before the tag is found. An
125 * error is printed if the tag is never found.
126 * -b string - Records are printed only back to the last reference
127 * to the string in the "module", "file" or
128 * "repository" fields.
130 * Field Selections [Simple comparisons on existing fields. All field
131 * selections are repeatable.]
134 * -u user - If no user is given and '-a' is not given, only
135 * records for the user typing the command are shown.
136 * ==> If -a or -u is not specified, just use "self".
138 * -f filematch - Only records in which the "file" field contains the
139 * string "filematch" are considered.
141 * -p repository - Only records in which the "repository" string is a
142 * prefix of the "repos" field are considered.
144 * -n modulename - Only records which contain "modulename" in the
145 * "module" field are considered.
148 * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
150 *** Checked out files for username. (default self, e.g. "dgg")
151 * cvs hi [equivalent to: "cvs hi -o -u dgg"]
152 * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
153 * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
155 *** Committed (modified) files from the beginning of the file.
156 * cvs hi -c [-u user]
158 *** Committed (modified) files since Midnight, January 1, 1990:
159 * cvs hi -c -D 'Jan 1 1990' [-u user]
161 *** Committed (modified) files since tag "TAG" was stored in the history file:
162 * cvs hi -c -t TAG [-u user]
164 *** Committed (modified) files since tag "TAG" was placed on the files:
165 * cvs hi -c -r TAG [-u user]
167 *** Who last committed file/repository X?
168 * cvs hi -c -l -[fp] X
170 *** Modified files since tag/date/file/repos?
171 * cvs hi -c {-r TAG | -D Date | -b string}
176 *** History of file/repository/module X.
179 *** History of user "user".
182 *** Dump (eXtract) specified record types
183 * cvs hi -x [TOEFWUPGCMAR]
186 * FUTURE: J[Join], I[Import] (Not currently implemented.)
196 char *type; /* Type of record (In history record) */
197 char *user; /* Username (In history record) */
198 char *dir; /* "Compressed" Working dir (In history record) */
199 char *repos; /* (Tag is special.) Repository (In history record) */
200 char *rev; /* Revision affected (In history record) */
201 char *file; /* Filename (In history record) */
202 char *end; /* Ptr into repository to copy at end of workdir */
203 char *mod; /* The module within which the file is contained */
204 time_t date; /* Calculated from date stored in record */
205 long idx; /* Index of record, for "stable" sort. */
207 static long hrec_idx;
210 static void fill_hrec PROTO((char *line, struct hrec * hr));
211 static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
212 static int select_hrec PROTO((struct hrec * hr));
213 static int sort_order PROTO((const PTR l, const PTR r));
214 static int within PROTO((char *find, char *string));
215 static void expand_modules PROTO((void));
216 static void read_hrecs PROTO((char *fname));
217 static void report_hrecs PROTO((void));
218 static void save_file PROTO((char *dir, char *name, char *module));
219 static void save_module PROTO((char *module));
220 static void save_user PROTO((char *name));
222 #define USER_INCREMENT 2
223 #define FILE_INCREMENT 128
224 #define MODULE_INCREMENT 5
225 #define HREC_INCREMENT 128
227 static short report_count;
229 static short extract;
230 static short extract_all;
231 static short v_checkout;
232 static short modified;
233 static short tag_report;
234 static short module_report;
235 static short working;
236 static short last_entry;
237 static short all_users;
239 static short user_sort;
240 static short repos_sort;
241 static short file_sort;
242 static short module_sort;
244 static short tz_local;
245 static time_t tz_seconds_east_of_GMT;
246 static char *tz_name = "+0000";
250 /* -r, -t, or -b options, malloc'd. These are "" if the option in
251 question is not specified or is overridden by another option. The
252 main reason for using "" rather than NULL is historical. Together
253 with since_date, these are a mutually exclusive set; one overrides the
255 static char *since_rev;
256 static char *since_tag;
258 /* -D option, or 0 if not specified. RCS format. */
259 static char * since_date;
261 static struct hrec *last_since_tag;
262 static struct hrec *last_backto;
264 /* Record types to look for, malloc'd. Probably could be statically
265 allocated, but only if we wanted to check for duplicates more than
267 static char *rec_types;
269 static size_t hrec_count;
270 static size_t hrec_max;
272 static char **user_list; /* Ptr to array of ptrs to user names */
273 static size_t user_max; /* Number of elements allocated */
274 static size_t user_count; /* Number of elements used */
276 static struct file_list_str
280 } *file_list; /* Ptr to array file name structs */
281 static size_t file_max; /* Number of elements allocated */
282 static size_t file_count; /* Number of elements used */
284 static char **mod_list; /* Ptr to array of ptrs to module names */
285 static size_t mod_max; /* Number of elements allocated */
286 static size_t mod_count; /* Number of elements used */
288 static char *histfile; /* Ptr to the history file name */
290 /* This is pretty unclear. First of all, separating "flags" vs.
291 "options" (I think the distinction is that "options" take arguments)
292 is nonstandard, and not something we do elsewhere in CVS. Second of
293 all, what does "reports" mean? I think it means that you can only
294 supply one of those options, but "reports" hardly has that meaning in
295 a self-explanatory way. */
296 static const char *const history_usg[] =
298 "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
300 " -T Produce report on all TAGs\n",
301 " -c Committed (Modified) files\n",
302 " -o Checked out modules\n",
303 " -m <module> Look for specified module (repeatable)\n",
304 " -x [" ALL_HISTORY_REC_TYPES "] Extract by record type\n",
305 " -e Everything (same as -x, but all record types)\n",
307 " -a All users (Default is self)\n",
308 " -l Last modified (committed or modified report)\n",
309 " -w Working directory must match\n",
311 " -D <date> Since date (Many formats)\n",
312 " -b <str> Back to record with str in module/file/repos field\n",
313 " -f <file> Specified file (same as command line) (repeatable)\n",
314 " -n <modulename> In module (repeatable)\n",
315 " -p <repos> In repository (repeatable)\n",
316 " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
317 " -t <tag> Since tag record placed in history file (by anyone).\n",
318 " -u <user> For user name (repeatable)\n",
319 " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
322 /* Sort routine for qsort:
323 - If a user is selected at all, sort it first. User-within-file is useless.
324 - If a module was selected explicitly, sort next on module.
325 - Then sort by file. "File" is "repository/file" unless "working" is set,
326 then it is "workdir/file". (Revision order should always track date.)
327 - Always sort timestamp last.
335 const struct hrec *left = (const struct hrec *) l;
336 const struct hrec *right = (const struct hrec *) r;
338 if (user_sort) /* If Sort by username, compare users */
340 if ((i = strcmp (left->user, right->user)) != 0)
343 if (module_sort) /* If sort by modules, compare module names */
345 if (left->mod && right->mod)
346 if ((i = strcmp (left->mod, right->mod)) != 0)
349 if (repos_sort) /* If sort by repository, compare them. */
351 if ((i = strcmp (left->repos, right->repos)) != 0)
354 if (file_sort) /* If sort by filename, compare files, NOT dirs. */
356 if ((i = strcmp (left->file, right->file)) != 0)
361 if ((i = strcmp (left->dir, right->dir)) != 0)
364 if ((i = strcmp (left->end, right->end)) != 0)
370 * By default, sort by date, time
371 * XXX: This fails after 2030 when date slides into sign bit
373 if ((i = ((long) (left->date) - (long) (right->date))) != 0)
376 /* For matching dates, keep the sort stable by using record index */
377 return (left->idx - right->idx);
391 since_rev = xstrdup ("");
392 since_tag = xstrdup ("");
393 backto = xstrdup ("");
394 rec_types = xstrdup ("");
396 while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
400 case 'T': /* Tag list */
404 case 'a': /* For all usernames */
415 rec_types = xstrdup (ALL_HISTORY_REC_TYPES);
417 case 'l': /* Find Last file record */
424 case 'w': /* Match Working Dir (CurDir) fields */
427 case 'X': /* Undocumented debugging flag */
433 case 'D': /* Since specified date */
434 if (*since_rev || *since_tag || *backto)
436 error (0, 0, "date overriding rev/tag/backto");
437 *since_rev = *since_tag = *backto = '\0';
439 since_date = Make_Date (optarg);
441 case 'b': /* Since specified file/Repos */
442 if (since_date || *since_rev || *since_tag)
444 error (0, 0, "backto overriding date/rev/tag");
445 *since_rev = *since_tag = '\0';
446 if (since_date != NULL)
451 backto = xstrdup (optarg);
453 case 'f': /* For specified file */
454 save_file (NULL, optarg, NULL);
456 case 'm': /* Full module report */
457 if (!module_report++) report_count++;
459 case 'n': /* Look for specified module */
460 save_module (optarg);
462 case 'p': /* For specified directory */
463 save_file (optarg, NULL, NULL);
465 case 'r': /* Since specified Tag/Rev */
466 if (since_date || *since_tag || *backto)
468 error (0, 0, "rev overriding date/tag/backto");
469 *since_tag = *backto = '\0';
470 if (since_date != NULL)
475 since_rev = xstrdup (optarg);
477 case 't': /* Since specified Tag/Rev */
478 if (since_date || *since_rev || *backto)
480 error (0, 0, "tag overriding date/marker/file/repos");
481 *since_rev = *backto = '\0';
482 if (since_date != NULL)
487 since_tag = xstrdup (optarg);
489 case 'u': /* For specified username */
498 for (cp = optarg; *cp; cp++)
499 if (!strchr (ALL_HISTORY_REC_TYPES, *cp))
500 error (1, 0, "%c is not a valid report type", *cp);
503 rec_types = xstrdup (optarg);
507 (optarg[0] == 'l' || optarg[0] == 'L')
508 && (optarg[1] == 't' || optarg[1] == 'T')
515 * Convert a known time with the given timezone to time_t.
516 * Use the epoch + 23 hours, so timezones east of GMT work.
518 static char f[] = "1/1/1970 23:00 %s";
519 char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
521 sprintf (buf, f, optarg);
522 t = get_date (buf, (struct timeb *) NULL);
524 if (t == (time_t) -1)
525 error (0, 0, "%s is not a known time zone", optarg);
529 * Convert to seconds east of GMT, removing the
530 * 23-hour offset mentioned above.
532 tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t;
545 for (i = 0; i < argc; i++)
546 save_file (NULL, argv[i], NULL);
549 /* ================ Now analyze the arguments a bit */
552 else if (report_count > 1)
553 error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
555 #ifdef CLIENT_SUPPORT
556 if (current_parsed_root->isremote)
558 struct file_list_str *f1;
561 /* We're the client side. Fire up the remote server. */
581 client_senddate (since_date);
582 if (backto[0] != '\0')
583 option_with_arg ("-b", backto);
584 for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
586 if (f1->l_file[0] == '*')
587 option_with_arg ("-p", f1->l_file + 1);
589 option_with_arg ("-f", f1->l_file);
593 for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
594 option_with_arg ("-n", *mod);
596 option_with_arg ("-r", since_rev);
598 option_with_arg ("-t", since_tag);
599 for (mod = user_list; mod < &user_list[user_count]; ++mod)
600 option_with_arg ("-u", *mod);
604 option_with_arg ("-x", rec_types);
605 option_with_arg ("-z", tz_name);
607 send_to_server ("history\012", 0);
608 return get_responses_and_close ();
620 if (!strchr (rec_types, 'T'))
622 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
623 (void) strcat (rec_types, "T");
626 else if (extract || extract_all)
634 rec_types = xstrdup ("MAR");
636 * If the user has not specified a date oriented flag ("Since"), sort
637 * by Repository/file before date. Default is "just" date.
640 || (!since_date && !*since_rev && !*since_tag && !*backto))
645 * If we are not looking for last_modified and the user specified
646 * one or more users to look at, sort by user before filename.
648 if (!last_entry && user_list)
652 else if (module_report)
655 rec_types = xstrdup (last_entry ? "OMAR" : ALL_HISTORY_REC_TYPES);
659 working = 0; /* User's workdir doesn't count here */
662 /* Must be "checkout" or default */
665 rec_types = xstrdup ("OF");
666 /* See comments in "modified" above */
667 if (!last_entry && user_list)
670 || (!since_date && !*since_rev && !*since_tag && !*backto))
674 /* If no users were specified, use self (-a saves a universal ("") user) */
676 save_user (getcaller ());
678 /* If we're looking back to a Tag value, must consider "Tag" records */
679 if (*since_tag && !strchr (rec_types, 'T'))
681 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
682 (void) strcat (rec_types, "T");
686 fname = xstrdup (histfile);
689 fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
690 + sizeof (CVSROOTADM_HISTORY) + 10);
691 (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
692 CVSROOTADM, CVSROOTADM_HISTORY);
698 qsort ((PTR) hrec_head, hrec_count,
699 sizeof (struct hrec), sort_order);
703 if (since_date != NULL)
714 history_write (type, update_dir, revs, name, repository)
716 const char *update_dir;
719 const char *repository;
723 char *username = getcaller ();
726 char *slash = "", *cp;
727 const char *cp2, *repos;
729 static char *tilde = "";
730 static char *PrCurDir = NULL;
732 if (logoff) /* History is turned off by noexec or
736 if (strchr (logHistory, type) == NULL)
738 fname = xmalloc (strlen (current_parsed_root->directory)
739 + sizeof (CVSROOTADM)
740 + sizeof (CVSROOTADM_HISTORY) + 3);
741 (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
742 CVSROOTADM, CVSROOTADM_HISTORY);
744 /* turn off history logging if the history file does not exist */
745 /* FIXME: This should check for write permissions instead. This way,
746 * O_CREATE could be added back into the call to open() below and
747 * there would be no race condition involved in log rotation.
749 * Note that the new method of turning off logging would be either via
750 * the CVSROOT/config file (probably the quicker method, but would need
751 * to be added, or at least checked for, too) or by creating a dummy
752 * history file with 0444 permissions.
761 fprintf (stderr, "%s-> fopen(%s,a)\n",
762 CLIENT_SERVER_STR, fname);
766 if (!history_lock (current_parsed_root->directory))
767 /* history_lock() will already have printed an error on failure. */
770 fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | OPEN_BINARY, 0666);
775 error (0, errno, "warning: cannot write to history file %s",
781 repos = Short_Repository (repository);
787 pwdir = get_homedir ();
791 /* Assumes neither CurDir nor pwdir ends in '/' */
793 if (!strncmp (CurDir, pwdir, i))
795 PrCurDir += i; /* Point to '/' separator */
800 /* Try harder to find a "homedir" */
801 struct saved_cwd cwd;
807 if (CVS_CHDIR (pwdir) < 0 || (homedir = xgetwd ()) == NULL)
810 if (restore_cwd (&cwd, NULL))
814 i = strlen (homedir);
815 if (!strncmp (CurDir, homedir, i))
817 PrCurDir += i; /* Point to '/' separator */
821 if (homedir != pwdir)
832 else if (update_dir && *update_dir)
837 workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash)
838 + strlen (update_dir) + 10);
839 (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
842 * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
843 * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
845 * "$workdir/$name" is the working file name.
846 * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
848 * First, note that the history format was intended to save space, not
849 * to be human readable.
851 * The working file directory ("workdir") and the Repository ("repos")
852 * usually end with the same one or more directory elements. To avoid
853 * duplication (and save space), the "workdir" field ends with
854 * an integer offset into the "repos" field. This offset indicates the
855 * beginning of the "tail" of "repos", after which all characters are
858 * In other words, if the "workdir" field has a '*' (a very stupid thing
859 * to put in a filename) in it, then every thing following the last '*'
860 * is a hex offset into "repos" of the first character from "repos" to
861 * append to "workdir" to finish the pathname.
863 * It might be easier to look at an example:
865 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
867 * Indicates that the workdir is really "~/work/cvs/examples", saving
868 * 10 characters, where "~/work*d" would save 6 characters and mean that
869 * the workdir is really "~/work/examples". It will mean more on
870 * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
872 * "workdir" is always an absolute pathname (~/xxx is an absolute path)
873 * "repos" is always a relative pathname. So we can assume that we will
874 * never run into the top of "workdir" -- there will always be a '/' or
875 * a '~' at the head of "workdir" that is not matched by anything in
876 * "repos". On the other hand, we *can* run off the top of "repos".
878 * Only "compress" if we save characters.
884 cp = workdir + strlen (workdir) - 1;
885 cp2 = repos + strlen (repos) - 1;
886 for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
891 i = strlen (repos) - i;
892 (void) sprintf ((cp + 1), "*%x", i);
897 line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
898 + strlen (revs) + strlen (name) + 100);
899 sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n",
900 type, (long) time ((time_t *) NULL),
901 username, workdir, repos, revs, name);
903 /* Lessen some race conditions on non-Posix-compliant hosts. */
904 if (lseek (fd, (off_t) 0, SEEK_END) == -1)
905 error (1, errno, "cannot seek to end of history file: %s", fname);
907 if (write (fd, line, strlen (line)) < 0)
908 error (1, errno, "cannot write to history file: %s", fname);
911 error (1, errno, "cannot close history file: %s", fname);
914 clear_history_lock ();
919 * save_user() adds a user name to the user list to select. Zero-length
920 * username ("") matches any user.
926 if (user_count == user_max)
928 user_max = xsum (user_max, USER_INCREMENT);
929 if (user_count == user_max
930 || size_overflow_p (xtimes (user_max, sizeof (char *))))
932 error (0, 0, "save_user: too many users");
935 user_list = xrealloc (user_list, xtimes (user_max, sizeof (char *)));
937 user_list[user_count++] = xstrdup (name);
941 * save_file() adds file name and associated module to the file list to select.
943 * If "dir" is null, store a file name as is.
944 * If "name" is null, store a directory name with a '*' on the front.
945 * Else, store concatenated "dir/name".
947 * Later, in the "select" stage:
948 * - if it starts with '*', it is prefix-matched against the repository.
949 * - if it has a '/' in it, it is matched against the repository/file.
950 * - else it is matched against the file name.
953 save_file (dir, name, module)
959 struct file_list_str *fl;
961 if (file_count == file_max)
963 file_max = xsum (file_max, FILE_INCREMENT);
964 if (file_count == file_max
965 || size_overflow_p (xtimes (file_max, sizeof (*fl))))
967 error (0, 0, "save_file: too many files");
970 file_list = xrealloc (file_list, xtimes (file_max, sizeof (*fl)));
972 fl = &file_list[file_count++];
973 fl->l_file = cp = xmalloc (dir ? strlen (dir) : 0
974 + name ? strlen (name) : 0
976 fl->l_module = module;
982 (void) strcpy (cp, dir);
983 (void) strcat (cp, "/");
984 (void) strcat (cp, name);
989 (void) strcpy (cp, dir);
996 (void) strcpy (cp, name);
1000 error (0, 0, "save_file: null dir and file name");
1006 save_module (module)
1009 if (mod_count == mod_max)
1011 mod_max = xsum (mod_max, MODULE_INCREMENT);
1012 if (mod_count == mod_max
1013 || size_overflow_p (xtimes (mod_max, sizeof (char *))))
1015 error (0, 0, "save_module: too many modules");
1018 mod_list = xrealloc (mod_list, xtimes (mod_max, sizeof (char *)));
1020 mod_list[mod_count++] = xstrdup (module);
1030 * Take a ptr to 7-part history line, ending with a newline, for example:
1032 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
1034 * Split it into 7 parts and drop the parts into a "struct hrec".
1035 * Return a pointer to the character following the newline.
1039 #define NEXT_BAR(here) do { \
1040 while (isspace(*line)) line++; \
1042 while ((c = *line++) && c != '|') ; \
1043 if (!c) return; line[-1] = '\0'; \
1047 fill_hrec (line, hr)
1054 hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
1055 hr->end = hr->mod = NULL;
1057 hr->idx = ++hrec_idx;
1059 while (isspace ((unsigned char) *line))
1063 hr->date = strtoul (line, &cp, 16);
1064 if (cp == line || *cp != '|')
1069 if ((cp = strrchr (hr->dir, '*')) != NULL)
1072 hr->end = line + strtoul (cp, NULL, 16);
1075 hr->end = line - 1; /* A handy pointer to '\0' */
1078 if (strchr ("FOET", *(hr->type)))
1085 #ifndef STAT_BLOCKSIZE
1086 #if HAVE_STRUCT_STAT_ST_BLKSIZE
1087 #define STAT_BLOCKSIZE(s) (s).st_blksize
1089 #define STAT_BLOCKSIZE(s) (4 * 1024)
1094 /* read_hrecs's job is to read the history file and fill in all the "hrec"
1095 * (history record) array elements with the ones we need to print.
1098 * - Read a block from the file.
1099 * - Walk through the block parsing line into hr records.
1100 * - if the hr isn't used, free its strings, if it is, bump the hrec counter
1101 * - at the end of a block, copy the end of the current block to the start
1102 * of space for the next block, then read in the next block. If we get less
1103 * than the whole block, we're done.
1109 unsigned char *cpstart, *cpend, *cp, *nl;
1115 if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
1116 error (1, errno, "cannot open history file: %s", fname);
1118 if (fstat (fd, &st_buf) < 0)
1119 error (1, errno, "can't stat history file");
1121 if (!(st_buf.st_size))
1122 error (1, 0, "history file is empty");
1124 cpstart = xmalloc (2 * STAT_BLOCKSIZE(st_buf));
1126 cp = cpend = cpstart;
1128 hrec_max = HREC_INCREMENT;
1129 hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
1134 for (nl = cp; nl < cpend && *nl != '\n'; nl++)
1135 if (!isprint(*nl)) *nl = ' ';
1139 if (nl - cp >= STAT_BLOCKSIZE(st_buf))
1141 error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
1142 (unsigned long) STAT_BLOCKSIZE(st_buf));
1145 memmove (cpstart, cp, nl - cp);
1146 nl = cpstart + (nl - cp);
1148 i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
1156 error (1, errno, "error reading history file");
1157 if (nl == cp) break;
1158 error (0, 0, "warning: no newline at end of history file");
1162 if (hrec_count == hrec_max)
1164 struct hrec *old_head = hrec_head;
1166 hrec_max = xsum (hrec_max, HREC_INCREMENT);
1167 if (hrec_count == hrec_max
1168 || size_overflow_p (xtimes (hrec_max, sizeof (struct hrec))))
1169 error (1, 0, "Too many history records in history file.");
1171 hrec_head = xrealloc (hrec_head,
1172 xtimes (hrec_max, sizeof (struct hrec)));
1174 last_since_tag = hrec_head + (last_since_tag - old_head);
1176 last_backto = hrec_head + (last_backto - old_head);
1179 /* fill_hrec dates from when history read the entire
1180 history file in one chunk, and then records were pulled out
1181 by pointing to the various parts of this big chunk. This is
1182 why there are ugly hacks here: I don't want to completely
1183 re-write the whole history stuff right now. */
1185 hrline = xstrdup ((char *)cp);
1186 fill_hrec (hrline, &hrec_head[hrec_count]);
1187 if (select_hrec (&hrec_head[hrec_count]))
1197 /* Special selection problem: If "since_tag" is set, we have saved every
1198 * record from the 1st occurrence of "since_tag", when we want to save
1199 * records since the *last* occurrence of "since_tag". So what we have
1200 * to do is bump hrec_head forward and reduce hrec_count accordingly.
1204 hrec_count -= (last_since_tag - hrec_head);
1205 hrec_head = last_since_tag;
1208 /* Much the same thing is necessary for the "backto" option. */
1211 hrec_count -= (last_backto - hrec_head);
1212 hrec_head = last_backto;
1216 /* Utility program for determining whether "find" is inside "string" */
1218 within (find, string)
1219 char *find, *string;
1223 if (!find || !string)
1227 len = strlen (find);
1231 if (!(string = strchr (string, c)))
1234 if (!strncmp (find, string, len))
1240 /* The purpose of "select_hrec" is to apply the selection criteria based on
1241 * the command arguments and defaults and return a flag indicating whether
1242 * this record should be remembered for printing.
1248 char **cpp, *cp, *cp2;
1249 struct file_list_str *fl;
1252 /* basic validity checking */
1253 if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
1254 !hr->file || !hr->end)
1256 error (0, 0, "warning: history line %ld invalid", hr->idx);
1260 /* "Since" checking: The argument parser guarantees that only one of the
1261 * following four choices is set:
1263 * 1. If "since_date" is set, it contains the date specified on the
1264 * command line. hr->date fields earlier than "since_date" are ignored.
1265 * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
1266 * number (which is of limited use) or a symbolic TAG. Each RCS file
1267 * is examined and the date on the specified revision (or the revision
1268 * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
1269 * compared against hr->date as in 1. above.
1270 * 3. If "since_tag" is set, matching tag records are saved. The field
1271 * "last_since_tag" is set to the last one of these. Since we don't
1272 * know where the last one will be, all records are saved from the
1273 * first occurrence of the TAG. Later, at the end of "select_hrec"
1274 * records before the last occurrence of "since_tag" are skipped.
1275 * 4. If "backto" is set, all records with a module name or file name
1276 * matching "backto" are saved. In addition, all records with a
1277 * repository field with a *prefix* matching "backto" are saved.
1278 * The field "last_backto" is set to the last one of these. As in
1279 * 3. above, "select_hrec" adjusts to include the last one later on.
1283 char *ourdate = date_from_time_t (hr->date);
1284 count = RCS_datecmp (ourdate, since_date);
1289 else if (*since_rev)
1293 struct file_info finfo;
1295 memset (&finfo, 0, sizeof finfo);
1296 finfo.file = hr->file;
1297 /* Not used, so don't worry about it. */
1298 finfo.update_dir = NULL;
1299 finfo.fullname = finfo.file;
1300 finfo.repository = hr->repos;
1301 finfo.entries = NULL;
1304 vers = Version_TS (&finfo, (char *) NULL, since_rev, (char *) NULL,
1308 if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
1313 freevers_ts (&vers);
1318 freevers_ts (&vers);
1320 else if (*since_tag)
1322 if (*(hr->type) == 'T')
1325 * A 'T'ag record, the "rev" field holds the tag to be set,
1326 * while the "repos" field holds "D"elete, "A"dd or a rev.
1328 if (within (since_tag, hr->rev))
1330 last_since_tag = hr;
1336 if (!last_since_tag)
1341 if (within (backto, hr->file) || within (backto, hr->mod) ||
1342 within (backto, hr->repos))
1350 * Run down "user_list", match username ("" matches anything)
1351 * If "" is not there and actual username is not there, return failure.
1353 if (user_list && hr->user)
1355 for (cpp = user_list, count = user_count; count; cpp++, count--)
1358 break; /* null user == accept */
1359 if (!strcmp (hr->user, *cpp)) /* found listed user */
1363 return (0); /* Not this user */
1366 /* Record type checking:
1368 * 1. If Record type is not in rec_types field, skip it.
1369 * 2. If mod_list is null, keep everything. Otherwise keep only modules
1371 * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
1372 * file_list is null, keep everything. Otherwise, keep only files on
1373 * file_list, matched appropriately.
1375 if (!strchr (rec_types, *(hr->type)))
1377 if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
1379 if (file_list) /* If file_list is null, accept all */
1381 for (fl = file_list, count = file_count; count; fl++, count--)
1383 /* 1. If file_list entry starts with '*', skip the '*' and
1384 * compare it against the repository in the hrec.
1385 * 2. If file_list entry has a '/' in it, compare it against
1386 * the concatenation of the repository and file from hrec.
1387 * 3. Else compare the file_list entry against the hrec file.
1389 char *cmpfile = NULL;
1391 if (*(cp = fl->l_file) == '*')
1394 /* if argument to -p is a prefix of repository */
1395 if (!strncmp (cp, hr->repos, strlen (cp)))
1397 hr->mod = fl->l_module;
1403 if (strchr (cp, '/'))
1405 cmpfile = xmalloc (strlen (hr->repos)
1408 (void) sprintf (cmpfile, "%s/%s",
1409 hr->repos, hr->file);
1417 /* if requested file is found within {repos}/file fields */
1418 if (within (cp, cp2))
1420 hr->mod = fl->l_module;
1421 if (cmpfile != NULL)
1425 if (cmpfile != NULL)
1430 return (0); /* String specified and no match */
1435 for (cpp = mod_list, count = mod_count; count; cpp++, count--)
1437 if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
1441 return (0); /* Module specified & this record is not one of them. */
1444 return (1); /* Select this record unless rejected above. */
1447 /* The "sort_order" routine (when handed to qsort) has arranged for the
1448 * hrecs files to be in the right order for the report.
1450 * Most of the "selections" are done in the select_hrec routine, but some
1451 * selections are more easily done after the qsort by "accept_hrec".
1456 struct hrec *hr, *lr;
1460 int user_len, file_len, rev_len, mod_len, repos_len;
1462 if (*since_tag && !last_since_tag)
1464 (void) printf ("No tag found: %s\n", since_tag);
1467 else if (*backto && !last_backto)
1469 (void) printf ("No module, file or repository with: %s\n", backto);
1472 else if (hrec_count < 1)
1474 (void) printf ("No records selected.\n");
1478 user_len = file_len = rev_len = mod_len = repos_len = 0;
1480 /* Run through lists and find maximum field widths */
1481 hr = lr = hrec_head;
1483 for (count = hrec_count; count--; lr = hr, hr++)
1489 if (!accept_hrec (lr, hr))
1493 repos = xstrdup (lr->repos);
1494 if ((cp = strrchr (repos, '/')) != NULL)
1496 if (lr->mod && !strcmp (++cp, lr->mod))
1498 (void) strcpy (cp, "*");
1501 if ((i = strlen (lr->user)) > user_len)
1503 if ((i = strlen (lr->file)) > file_len)
1505 if (ty != 'T' && (i = strlen (repos)) > repos_len)
1507 if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
1509 if (lr->mod && (i = strlen (lr->mod)) > mod_len)
1514 /* Walk through hrec array setting "lr" (Last Record) to each element.
1515 * "hr" points to the record following "lr" -- It is NULL in the last
1518 * There are two sections in the loop below:
1519 * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
1520 * decide whether the record should be printed.
1521 * 2. Based on the record type, format and print the data.
1523 for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
1530 if (!accept_hrec (lr, hr))
1536 time_t t = lr->date + tz_seconds_east_of_GMT;
1540 tm = localtime (&(lr->date));
1542 (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
1543 tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
1544 tm->tm_min, tz_name, user_len, lr->user);
1546 workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
1547 (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
1548 if ((cp = strrchr (workdir, '/')) != NULL)
1550 if (lr->mod && !strcmp (++cp, lr->mod))
1552 (void) strcpy (cp, "*");
1555 repos = xmalloc (strlen (lr->repos) + 10);
1556 (void) strcpy (repos, lr->repos);
1557 if ((cp = strrchr (repos, '/')) != NULL)
1559 if (lr->mod && !strcmp (++cp, lr->mod))
1561 (void) strcpy (cp, "*");
1568 /* 'T'ag records: repository is a "tag type", rev is the tag */
1569 (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
1572 (void) printf (" {%s}", workdir);
1577 if (lr->rev && *(lr->rev))
1578 (void) printf (" [%s]", lr->rev);
1579 (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
1580 mod_len + 1 - (int) strlen (lr->mod),
1591 (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
1592 file_len, lr->file, repos_len, repos,
1593 lr->mod ? lr->mod : "", workdir);
1596 (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
1599 (void) putchar ('\n');
1606 accept_hrec (lr, hr)
1607 struct hrec *hr, *lr;
1613 if (last_since_tag && ty == 'T')
1619 return (0); /* Only interested in 'O' records */
1621 /* We want to identify all the states that cause the next record
1622 * ("hr") to be different from the current one ("lr") and only
1623 * print a line at the allowed boundaries.
1626 if (!hr || /* The last record */
1627 strcmp (hr->user, lr->user) || /* User has changed */
1628 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1629 (working && /* If must match "workdir" */
1630 (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1631 strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
1637 if (!last_entry || /* Don't want only last rec */
1638 !hr || /* Last entry is a "last entry" */
1639 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1640 strcmp (hr->file, lr->file))/* File has changed */
1644 { /* If must match "workdir" */
1645 if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1646 strcmp (hr->end, lr->end)) /* the 2nd parts differ */
1650 else if (module_report)
1652 if (!last_entry || /* Don't want only last rec */
1653 !hr || /* Last entry is a "last entry" */
1654 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1655 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1656 strcmp (hr->file, lr->file))/* File has changed */
1661 /* "extract" and "tag_report" always print selected records. */