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.)
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 PROTO((char *line, struct hrec * hr));
202 static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
203 static int select_hrec PROTO((struct hrec * hr));
204 static int sort_order PROTO((const PTR l, const PTR r));
205 static int within PROTO((char *find, char *string));
206 static void expand_modules PROTO((void));
207 static void read_hrecs PROTO((char *fname));
208 static void report_hrecs PROTO((void));
209 static void save_file PROTO((char *dir, char *name, char *module));
210 static void save_module PROTO((char *module));
211 static void save_user PROTO((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 char *logHistory = ALL_HISTORY_REC_TYPES;
241 /* -r, -t, or -b options, malloc'd. These are "" if the option in
242 question is not specified or is overridden by another option. The
243 main reason for using "" rather than NULL is historical. Together
244 with since_date, these are a mutually exclusive set; one overrides the
246 static char *since_rev;
247 static char *since_tag;
249 /* -D option, or 0 if not specified. RCS format. */
250 static char * since_date;
252 static struct hrec *last_since_tag;
253 static struct hrec *last_backto;
255 /* Record types to look for, malloc'd. Probably could be statically
256 allocated, but only if we wanted to check for duplicates more than
258 static char *rec_types;
260 static int hrec_count;
263 static char **user_list; /* Ptr to array of ptrs to user names */
264 static int user_max; /* Number of elements allocated */
265 static int user_count; /* Number of elements used */
267 static struct file_list_str
271 } *file_list; /* Ptr to array file name structs */
272 static int file_max; /* Number of elements allocated */
273 static int file_count; /* Number of elements used */
275 static char **mod_list; /* Ptr to array of ptrs to module names */
276 static int mod_max; /* Number of elements allocated */
277 static int mod_count; /* Number of elements used */
279 static char *histfile; /* Ptr to the history file name */
281 /* This is pretty unclear. First of all, separating "flags" vs.
282 "options" (I think the distinction is that "options" take arguments)
283 is nonstandard, and not something we do elsewhere in CVS. Second of
284 all, what does "reports" mean? I think it means that you can only
285 supply one of those options, but "reports" hardly has that meaning in
286 a self-explanatory way. */
287 static const char *const history_usg[] =
289 "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
291 " -T Produce report on all TAGs\n",
292 " -c Committed (Modified) files\n",
293 " -o Checked out modules\n",
294 " -m <module> Look for specified module (repeatable)\n",
295 " -x [" ALL_HISTORY_REC_TYPES "] Extract by record type\n",
296 " -e Everything (same as -x, but all record types)\n",
298 " -a All users (Default is self)\n",
299 " -l Last modified (committed or modified report)\n",
300 " -w Working directory must match\n",
302 " -D <date> Since date (Many formats)\n",
303 " -b <str> Back to record with str in module/file/repos field\n",
304 " -f <file> Specified file (same as command line) (repeatable)\n",
305 " -n <modulename> In module (repeatable)\n",
306 " -p <repos> In repository (repeatable)\n",
307 " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
308 " -t <tag> Since tag record placed in history file (by anyone).\n",
309 " -u <user> For user name (repeatable)\n",
310 " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
313 /* Sort routine for qsort:
314 - If a user is selected at all, sort it first. User-within-file is useless.
315 - If a module was selected explicitly, sort next on module.
316 - Then sort by file. "File" is "repository/file" unless "working" is set,
317 then it is "workdir/file". (Revision order should always track date.)
318 - Always sort timestamp last.
326 const struct hrec *left = (const struct hrec *) l;
327 const struct hrec *right = (const struct hrec *) r;
329 if (user_sort) /* If Sort by username, compare users */
331 if ((i = strcmp (left->user, right->user)) != 0)
334 if (module_sort) /* If sort by modules, compare module names */
336 if (left->mod && right->mod)
337 if ((i = strcmp (left->mod, right->mod)) != 0)
340 if (repos_sort) /* If sort by repository, compare them. */
342 if ((i = strcmp (left->repos, right->repos)) != 0)
345 if (file_sort) /* If sort by filename, compare files, NOT dirs. */
347 if ((i = strcmp (left->file, right->file)) != 0)
352 if ((i = strcmp (left->dir, right->dir)) != 0)
355 if ((i = strcmp (left->end, right->end)) != 0)
361 * By default, sort by date, time
362 * XXX: This fails after 2030 when date slides into sign bit
364 if ((i = ((long) (left->date) - (long) (right->date))) != 0)
367 /* For matching dates, keep the sort stable by using record index */
368 return (left->idx - right->idx);
382 since_rev = xstrdup ("");
383 since_tag = xstrdup ("");
384 backto = xstrdup ("");
385 rec_types = xstrdup ("");
387 while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
391 case 'T': /* Tag list */
395 case 'a': /* For all usernames */
406 rec_types = xstrdup (ALL_HISTORY_REC_TYPES);
408 case 'l': /* Find Last file record */
415 case 'w': /* Match Working Dir (CurDir) fields */
418 case 'X': /* Undocumented debugging flag */
424 case 'D': /* Since specified date */
425 if (*since_rev || *since_tag || *backto)
427 error (0, 0, "date overriding rev/tag/backto");
428 *since_rev = *since_tag = *backto = '\0';
430 since_date = Make_Date (optarg);
432 case 'b': /* Since specified file/Repos */
433 if (since_date || *since_rev || *since_tag)
435 error (0, 0, "backto overriding date/rev/tag");
436 *since_rev = *since_tag = '\0';
437 if (since_date != NULL)
442 backto = xstrdup (optarg);
444 case 'f': /* For specified file */
445 save_file ("", optarg, (char *) NULL);
447 case 'm': /* Full module report */
448 if (!module_report++) report_count++;
450 case 'n': /* Look for specified module */
451 save_module (optarg);
453 case 'p': /* For specified directory */
454 save_file (optarg, "", (char *) NULL);
456 case 'r': /* Since specified Tag/Rev */
457 if (since_date || *since_tag || *backto)
459 error (0, 0, "rev overriding date/tag/backto");
460 *since_tag = *backto = '\0';
461 if (since_date != NULL)
466 since_rev = xstrdup (optarg);
468 case 't': /* Since specified Tag/Rev */
469 if (since_date || *since_rev || *backto)
471 error (0, 0, "tag overriding date/marker/file/repos");
472 *since_rev = *backto = '\0';
473 if (since_date != NULL)
478 since_tag = xstrdup (optarg);
480 case 'u': /* For specified username */
489 for (cp = optarg; *cp; cp++)
490 if (!strchr (ALL_HISTORY_REC_TYPES, *cp))
491 error (1, 0, "%c is not a valid report type", *cp);
494 rec_types = xstrdup (optarg);
498 (optarg[0] == 'l' || optarg[0] == 'L')
499 && (optarg[1] == 't' || optarg[1] == 'T')
506 * Convert a known time with the given timezone to time_t.
507 * Use the epoch + 23 hours, so timezones east of GMT work.
509 static char f[] = "1/1/1970 23:00 %s";
510 char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
512 sprintf (buf, f, optarg);
513 t = get_date (buf, (struct timeb *) NULL);
515 if (t == (time_t) -1)
516 error (0, 0, "%s is not a known time zone", optarg);
520 * Convert to seconds east of GMT, removing the
521 * 23-hour offset mentioned above.
523 tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t;
536 for (i = 0; i < argc; i++)
537 save_file ("", argv[i], (char *) NULL);
540 /* ================ Now analyze the arguments a bit */
543 else if (report_count > 1)
544 error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
546 #ifdef CLIENT_SUPPORT
547 if (current_parsed_root->isremote)
549 struct file_list_str *f1;
552 /* We're the client side. Fire up the remote server. */
572 client_senddate (since_date);
573 if (backto[0] != '\0')
574 option_with_arg ("-b", backto);
575 for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
577 if (f1->l_file[0] == '*')
578 option_with_arg ("-p", f1->l_file + 1);
580 option_with_arg ("-f", f1->l_file);
584 for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
585 option_with_arg ("-n", *mod);
587 option_with_arg ("-r", since_rev);
589 option_with_arg ("-t", since_tag);
590 for (mod = user_list; mod < &user_list[user_count]; ++mod)
591 option_with_arg ("-u", *mod);
595 option_with_arg ("-x", rec_types);
596 option_with_arg ("-z", tz_name);
598 send_to_server ("history\012", 0);
599 return get_responses_and_close ();
611 if (!strchr (rec_types, 'T'))
613 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
614 (void) strcat (rec_types, "T");
617 else if (extract || extract_all)
625 rec_types = xstrdup ("MAR");
627 * If the user has not specified a date oriented flag ("Since"), sort
628 * by Repository/file before date. Default is "just" date.
631 || (!since_date && !*since_rev && !*since_tag && !*backto))
636 * If we are not looking for last_modified and the user specified
637 * one or more users to look at, sort by user before filename.
639 if (!last_entry && user_list)
643 else if (module_report)
646 rec_types = xstrdup (last_entry ? "OMAR" : ALL_HISTORY_REC_TYPES);
650 working = 0; /* User's workdir doesn't count here */
653 /* Must be "checkout" or default */
656 rec_types = xstrdup ("OF");
657 /* See comments in "modified" above */
658 if (!last_entry && user_list)
661 || (!since_date && !*since_rev && !*since_tag && !*backto))
665 /* If no users were specified, use self (-a saves a universal ("") user) */
667 save_user (getcaller ());
669 /* If we're looking back to a Tag value, must consider "Tag" records */
670 if (*since_tag && !strchr (rec_types, 'T'))
672 rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
673 (void) strcat (rec_types, "T");
677 fname = xstrdup (histfile);
680 fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
681 + sizeof (CVSROOTADM_HISTORY) + 10);
682 (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
683 CVSROOTADM, CVSROOTADM_HISTORY);
689 qsort ((PTR) hrec_head, hrec_count,
690 sizeof (struct hrec), sort_order);
694 if (since_date != NULL)
705 history_write (type, update_dir, revs, name, repository)
707 const char *update_dir;
710 const char *repository;
714 char *username = getcaller ();
717 char *slash = "", *cp;
718 const char *cp2, *repos;
720 static char *tilde = "";
721 static char *PrCurDir = NULL;
723 if (logoff) /* History is turned off by noexec or
727 if ( strchr(logHistory, type) == NULL )
729 fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
730 + sizeof (CVSROOTADM_HISTORY) + 3);
731 (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
732 CVSROOTADM, CVSROOTADM_HISTORY);
734 /* turn off history logging if the history file does not exist */
735 /* FIXME: This should check for write permissions instead. This way,
736 * O_CREATE could be added back into the call to open() below and
737 * there would be no race condition involved in log rotation.
739 * Note that the new method of turning off logging would be either via
740 * the CVSROOT/config file (probably the quicker method, but would need
741 * to be added, or at least checked for, too) or by creating a dummy
742 * history file with 0444 permissions.
751 fprintf (stderr, "%s-> fopen(%s,a)\n",
752 CLIENT_SERVER_STR, fname);
755 fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | OPEN_BINARY, 0666);
760 error (0, errno, "warning: cannot write to history file %s",
766 repos = Short_Repository (repository);
772 pwdir = get_homedir ();
776 /* Assumes neither CurDir nor pwdir ends in '/' */
778 if (!strncmp (CurDir, pwdir, i))
780 PrCurDir += i; /* Point to '/' separator */
785 /* Try harder to find a "homedir" */
786 struct saved_cwd cwd;
792 if ( CVS_CHDIR (pwdir) < 0 || (homedir = xgetwd ()) == NULL)
795 if (restore_cwd (&cwd, NULL))
799 i = strlen (homedir);
800 if (!strncmp (CurDir, homedir, i))
802 PrCurDir += i; /* Point to '/' separator */
806 if (homedir != pwdir)
817 else if (update_dir && *update_dir)
822 workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash)
823 + strlen (update_dir) + 10);
824 (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
827 * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
828 * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
830 * "$workdir/$name" is the working file name.
831 * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
833 * First, note that the history format was intended to save space, not
834 * to be human readable.
836 * The working file directory ("workdir") and the Repository ("repos")
837 * usually end with the same one or more directory elements. To avoid
838 * duplication (and save space), the "workdir" field ends with
839 * an integer offset into the "repos" field. This offset indicates the
840 * beginning of the "tail" of "repos", after which all characters are
843 * In other words, if the "workdir" field has a '*' (a very stupid thing
844 * to put in a filename) in it, then every thing following the last '*'
845 * is a hex offset into "repos" of the first character from "repos" to
846 * append to "workdir" to finish the pathname.
848 * It might be easier to look at an example:
850 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
852 * Indicates that the workdir is really "~/work/cvs/examples", saving
853 * 10 characters, where "~/work*d" would save 6 characters and mean that
854 * the workdir is really "~/work/examples". It will mean more on
855 * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
857 * "workdir" is always an absolute pathname (~/xxx is an absolute path)
858 * "repos" is always a relative pathname. So we can assume that we will
859 * never run into the top of "workdir" -- there will always be a '/' or
860 * a '~' at the head of "workdir" that is not matched by anything in
861 * "repos". On the other hand, we *can* run off the top of "repos".
863 * Only "compress" if we save characters.
869 cp = workdir + strlen (workdir) - 1;
870 cp2 = repos + strlen (repos) - 1;
871 for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
876 i = strlen (repos) - i;
877 (void) sprintf ((cp + 1), "*%x", i);
882 line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
883 + strlen (revs) + strlen (name) + 100);
884 sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n",
885 type, (long) time ((time_t *) NULL),
886 username, workdir, repos, revs, name);
888 /* Lessen some race conditions on non-Posix-compliant hosts. */
889 if (lseek (fd, (off_t) 0, SEEK_END) == -1)
890 error (1, errno, "cannot seek to end of history file: %s", fname);
892 if (write (fd, line, strlen (line)) < 0)
893 error (1, errno, "cannot write to history file: %s", fname);
896 error (1, errno, "cannot close history file: %s", fname);
903 * save_user() adds a user name to the user list to select. Zero-length
904 * username ("") matches any user.
910 if (user_count == user_max)
912 user_max = xsum (user_max, USER_INCREMENT);
913 if (size_overflow_p (xtimes (user_max, sizeof (char *))))
915 error (0, 0, "save_user: too many users");
918 user_list = xrealloc (user_list, xtimes (user_max, sizeof (char *)));
920 user_list[user_count++] = xstrdup (name);
924 * save_file() adds file name and associated module to the file list to select.
926 * If "dir" is null, store a file name as is.
927 * If "name" is null, store a directory name with a '*' on the front.
928 * Else, store concatenated "dir/name".
930 * Later, in the "select" stage:
931 * - if it starts with '*', it is prefix-matched against the repository.
932 * - if it has a '/' in it, it is matched against the repository/file.
933 * - else it is matched against the file name.
936 save_file (dir, name, module)
942 struct file_list_str *fl;
944 if (file_count == file_max)
946 file_max = xsum (file_max, FILE_INCREMENT);
947 if (size_overflow_p (xtimes (file_max, sizeof (*fl))))
949 error (0, 0, "save_file: too many files");
952 file_list = xrealloc (file_list, xtimes (file_max, sizeof (*fl)));
954 fl = &file_list[file_count++];
955 fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
956 fl->l_module = module;
962 (void) strcpy (cp, dir);
963 (void) strcat (cp, "/");
964 (void) strcat (cp, name);
969 (void) strcpy (cp, dir);
976 (void) strcpy (cp, name);
980 error (0, 0, "save_file: null dir and file name");
989 if (mod_count == mod_max)
991 mod_max = xsum (mod_max, MODULE_INCREMENT);
992 if (size_overflow_p (xtimes (mod_max, sizeof (char *))))
994 error (0, 0, "save_module: too many modules");
997 mod_list = xrealloc (mod_list, xtimes (mod_max, sizeof (char *)));
999 mod_list[mod_count++] = xstrdup (module);
1009 * Take a ptr to 7-part history line, ending with a newline, for example:
1011 * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
1013 * Split it into 7 parts and drop the parts into a "struct hrec".
1014 * Return a pointer to the character following the newline.
1018 #define NEXT_BAR(here) do { \
1019 while (isspace(*line)) line++; \
1021 while ((c = *line++) && c != '|') ; \
1022 if (!c) return; line[-1] = '\0'; \
1026 fill_hrec (line, hr)
1033 hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
1034 hr->end = hr->mod = NULL;
1036 hr->idx = ++hrec_idx;
1038 while (isspace ((unsigned char) *line))
1042 hr->date = strtoul (line, &cp, 16);
1043 if (cp == line || *cp != '|')
1048 if ((cp = strrchr (hr->dir, '*')) != NULL)
1051 hr->end = line + strtoul (cp, NULL, 16);
1054 hr->end = line - 1; /* A handy pointer to '\0' */
1057 if (strchr ("FOET", *(hr->type)))
1064 #ifndef STAT_BLOCKSIZE
1065 #if HAVE_STRUCT_STAT_ST_BLKSIZE
1066 #define STAT_BLOCKSIZE(s) (s).st_blksize
1068 #define STAT_BLOCKSIZE(s) (4 * 1024)
1073 /* read_hrecs's job is to read the history file and fill in all the "hrec"
1074 * (history record) array elements with the ones we need to print.
1077 * - Read a block from the file.
1078 * - Walk through the block parsing line into hr records.
1079 * - if the hr isn't used, free its strings, if it is, bump the hrec counter
1080 * - at the end of a block, copy the end of the current block to the start
1081 * of space for the next block, then read in the next block. If we get less
1082 * than the whole block, we're done.
1088 unsigned char *cpstart, *cpend, *cp, *nl;
1094 if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
1095 error (1, errno, "cannot open history file: %s", fname);
1097 if (fstat (fd, &st_buf) < 0)
1098 error (1, errno, "can't stat history file");
1100 if (!(st_buf.st_size))
1101 error (1, 0, "history file is empty");
1103 cpstart = xmalloc (2 * STAT_BLOCKSIZE(st_buf));
1105 cp = cpend = cpstart;
1107 hrec_max = HREC_INCREMENT;
1108 hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
1113 for (nl = cp; nl < cpend && *nl != '\n'; nl++)
1114 if (!isprint(*nl)) *nl = ' ';
1118 if (nl - cp >= STAT_BLOCKSIZE(st_buf))
1120 error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
1121 (unsigned long) STAT_BLOCKSIZE(st_buf));
1124 memmove (cpstart, cp, nl - cp);
1125 nl = cpstart + (nl - cp);
1127 i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
1135 error (1, errno, "error reading history file");
1136 if (nl == cp) break;
1137 error (0, 0, "warning: no newline at end of history file");
1141 if (hrec_count == hrec_max)
1143 struct hrec *old_head = hrec_head;
1145 hrec_max += HREC_INCREMENT;
1146 hrec_head = xrealloc ((char *) hrec_head,
1147 hrec_max * sizeof (struct hrec));
1149 last_since_tag = hrec_head + (last_since_tag - old_head);
1151 last_backto = hrec_head + (last_backto - old_head);
1154 /* fill_hrec dates from when history read the entire
1155 history file in one chunk, and then records were pulled out
1156 by pointing to the various parts of this big chunk. This is
1157 why there are ugly hacks here: I don't want to completely
1158 re-write the whole history stuff right now. */
1160 hrline = xstrdup ((char *)cp);
1161 fill_hrec (hrline, &hrec_head[hrec_count]);
1162 if (select_hrec (&hrec_head[hrec_count]))
1172 /* Special selection problem: If "since_tag" is set, we have saved every
1173 * record from the 1st occurrence of "since_tag", when we want to save
1174 * records since the *last* occurrence of "since_tag". So what we have
1175 * to do is bump hrec_head forward and reduce hrec_count accordingly.
1179 hrec_count -= (last_since_tag - hrec_head);
1180 hrec_head = last_since_tag;
1183 /* Much the same thing is necessary for the "backto" option. */
1186 hrec_count -= (last_backto - hrec_head);
1187 hrec_head = last_backto;
1191 /* Utility program for determining whether "find" is inside "string" */
1193 within (find, string)
1194 char *find, *string;
1198 if (!find || !string)
1202 len = strlen (find);
1206 if (!(string = strchr (string, c)))
1209 if (!strncmp (find, string, len))
1215 /* The purpose of "select_hrec" is to apply the selection criteria based on
1216 * the command arguments and defaults and return a flag indicating whether
1217 * this record should be remembered for printing.
1223 char **cpp, *cp, *cp2;
1224 struct file_list_str *fl;
1227 /* basic validity checking */
1228 if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
1229 !hr->file || !hr->end)
1231 error (0, 0, "warning: history line %ld invalid", hr->idx);
1235 /* "Since" checking: The argument parser guarantees that only one of the
1236 * following four choices is set:
1238 * 1. If "since_date" is set, it contains the date specified on the
1239 * command line. hr->date fields earlier than "since_date" are ignored.
1240 * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
1241 * number (which is of limited use) or a symbolic TAG. Each RCS file
1242 * is examined and the date on the specified revision (or the revision
1243 * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
1244 * compared against hr->date as in 1. above.
1245 * 3. If "since_tag" is set, matching tag records are saved. The field
1246 * "last_since_tag" is set to the last one of these. Since we don't
1247 * know where the last one will be, all records are saved from the
1248 * first occurrence of the TAG. Later, at the end of "select_hrec"
1249 * records before the last occurrence of "since_tag" are skipped.
1250 * 4. If "backto" is set, all records with a module name or file name
1251 * matching "backto" are saved. In addition, all records with a
1252 * repository field with a *prefix* matching "backto" are saved.
1253 * The field "last_backto" is set to the last one of these. As in
1254 * 3. above, "select_hrec" adjusts to include the last one later on.
1258 char *ourdate = date_from_time_t (hr->date);
1259 count = RCS_datecmp (ourdate, since_date);
1264 else if (*since_rev)
1268 struct file_info finfo;
1270 memset (&finfo, 0, sizeof finfo);
1271 finfo.file = hr->file;
1272 /* Not used, so don't worry about it. */
1273 finfo.update_dir = NULL;
1274 finfo.fullname = finfo.file;
1275 finfo.repository = hr->repos;
1276 finfo.entries = NULL;
1279 vers = Version_TS (&finfo, (char *) NULL, since_rev, (char *) NULL,
1283 if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
1288 freevers_ts (&vers);
1293 freevers_ts (&vers);
1295 else if (*since_tag)
1297 if (*(hr->type) == 'T')
1300 * A 'T'ag record, the "rev" field holds the tag to be set,
1301 * while the "repos" field holds "D"elete, "A"dd or a rev.
1303 if (within (since_tag, hr->rev))
1305 last_since_tag = hr;
1311 if (!last_since_tag)
1316 if (within (backto, hr->file) || within (backto, hr->mod) ||
1317 within (backto, hr->repos))
1325 * Run down "user_list", match username ("" matches anything)
1326 * If "" is not there and actual username is not there, return failure.
1328 if (user_list && hr->user)
1330 for (cpp = user_list, count = user_count; count; cpp++, count--)
1333 break; /* null user == accept */
1334 if (!strcmp (hr->user, *cpp)) /* found listed user */
1338 return (0); /* Not this user */
1341 /* Record type checking:
1343 * 1. If Record type is not in rec_types field, skip it.
1344 * 2. If mod_list is null, keep everything. Otherwise keep only modules
1346 * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
1347 * file_list is null, keep everything. Otherwise, keep only files on
1348 * file_list, matched appropriately.
1350 if (!strchr (rec_types, *(hr->type)))
1352 if (!strchr ("TFOE", *(hr->type))) /* Don't bother with "file" if "TFOE" */
1354 if (file_list) /* If file_list is null, accept all */
1356 for (fl = file_list, count = file_count; count; fl++, count--)
1358 /* 1. If file_list entry starts with '*', skip the '*' and
1359 * compare it against the repository in the hrec.
1360 * 2. If file_list entry has a '/' in it, compare it against
1361 * the concatenation of the repository and file from hrec.
1362 * 3. Else compare the file_list entry against the hrec file.
1364 char *cmpfile = NULL;
1366 if (*(cp = fl->l_file) == '*')
1369 /* if argument to -p is a prefix of repository */
1370 if (!strncmp (cp, hr->repos, strlen (cp)))
1372 hr->mod = fl->l_module;
1378 if (strchr (cp, '/'))
1380 cmpfile = xmalloc (strlen (hr->repos)
1383 (void) sprintf (cmpfile, "%s/%s",
1384 hr->repos, hr->file);
1392 /* if requested file is found within {repos}/file fields */
1393 if (within (cp, cp2))
1395 hr->mod = fl->l_module;
1398 if (cmpfile != NULL)
1403 return (0); /* String specified and no match */
1408 for (cpp = mod_list, count = mod_count; count; cpp++, count--)
1410 if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
1414 return (0); /* Module specified & this record is not one of them. */
1417 return (1); /* Select this record unless rejected above. */
1420 /* The "sort_order" routine (when handed to qsort) has arranged for the
1421 * hrecs files to be in the right order for the report.
1423 * Most of the "selections" are done in the select_hrec routine, but some
1424 * selections are more easily done after the qsort by "accept_hrec".
1429 struct hrec *hr, *lr;
1433 int user_len, file_len, rev_len, mod_len, repos_len;
1435 if (*since_tag && !last_since_tag)
1437 (void) printf ("No tag found: %s\n", since_tag);
1440 else if (*backto && !last_backto)
1442 (void) printf ("No module, file or repository with: %s\n", backto);
1445 else if (hrec_count < 1)
1447 (void) printf ("No records selected.\n");
1451 user_len = file_len = rev_len = mod_len = repos_len = 0;
1453 /* Run through lists and find maximum field widths */
1454 hr = lr = hrec_head;
1456 for (count = hrec_count; count--; lr = hr, hr++)
1462 if (!accept_hrec (lr, hr))
1466 repos = xstrdup (lr->repos);
1467 if ((cp = strrchr (repos, '/')) != NULL)
1469 if (lr->mod && !strcmp (++cp, lr->mod))
1471 (void) strcpy (cp, "*");
1474 if ((i = strlen (lr->user)) > user_len)
1476 if ((i = strlen (lr->file)) > file_len)
1478 if (ty != 'T' && (i = strlen (repos)) > repos_len)
1480 if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
1482 if (lr->mod && (i = strlen (lr->mod)) > mod_len)
1487 /* Walk through hrec array setting "lr" (Last Record) to each element.
1488 * "hr" points to the record following "lr" -- It is NULL in the last
1491 * There are two sections in the loop below:
1492 * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
1493 * decide whether the record should be printed.
1494 * 2. Based on the record type, format and print the data.
1496 for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
1503 if (!accept_hrec (lr, hr))
1509 time_t t = lr->date + tz_seconds_east_of_GMT;
1513 tm = localtime (&(lr->date));
1515 (void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
1516 tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
1517 tm->tm_min, tz_name, user_len, lr->user);
1519 workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
1520 (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
1521 if ((cp = strrchr (workdir, '/')) != NULL)
1523 if (lr->mod && !strcmp (++cp, lr->mod))
1525 (void) strcpy (cp, "*");
1528 repos = xmalloc (strlen (lr->repos) + 10);
1529 (void) strcpy (repos, lr->repos);
1530 if ((cp = strrchr (repos, '/')) != NULL)
1532 if (lr->mod && !strcmp (++cp, lr->mod))
1534 (void) strcpy (cp, "*");
1541 /* 'T'ag records: repository is a "tag type", rev is the tag */
1542 (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
1545 (void) printf (" {%s}", workdir);
1550 if (lr->rev && *(lr->rev))
1551 (void) printf (" [%s]", lr->rev);
1552 (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
1553 mod_len + 1 - (int) strlen (lr->mod),
1564 (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
1565 file_len, lr->file, repos_len, repos,
1566 lr->mod ? lr->mod : "", workdir);
1569 (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
1572 (void) putchar ('\n');
1579 accept_hrec (lr, hr)
1580 struct hrec *hr, *lr;
1586 if (last_since_tag && ty == 'T')
1592 return (0); /* Only interested in 'O' records */
1594 /* We want to identify all the states that cause the next record
1595 * ("hr") to be different from the current one ("lr") and only
1596 * print a line at the allowed boundaries.
1599 if (!hr || /* The last record */
1600 strcmp (hr->user, lr->user) || /* User has changed */
1601 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1602 (working && /* If must match "workdir" */
1603 (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1604 strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
1610 if (!last_entry || /* Don't want only last rec */
1611 !hr || /* Last entry is a "last entry" */
1612 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1613 strcmp (hr->file, lr->file))/* File has changed */
1617 { /* If must match "workdir" */
1618 if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
1619 strcmp (hr->end, lr->end)) /* the 2nd parts differ */
1623 else if (module_report)
1625 if (!last_entry || /* Don't want only last rec */
1626 !hr || /* Last entry is a "last entry" */
1627 strcmp (hr->mod, lr->mod) ||/* Module has changed */
1628 strcmp (hr->repos, lr->repos) || /* Repository has changed */
1629 strcmp (hr->file, lr->file))/* File has changed */
1634 /* "extract" and "tag_report" always print selected records. */