xref: /openbsd/gnu/usr.bin/cvs/src/history.c (revision 4b3d160c)
11e72d8d2Sderaadt /*
21e72d8d2Sderaadt  *
31e72d8d2Sderaadt  *    You may distribute under the terms of the GNU General Public License
41e72d8d2Sderaadt  *    as specified in the README file that comes with the CVS 1.0 kit.
51e72d8d2Sderaadt  *
61e72d8d2Sderaadt  * **************** History of Users and Module ****************
71e72d8d2Sderaadt  *
81e72d8d2Sderaadt  * LOGGING:  Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
91e72d8d2Sderaadt  *
101e72d8d2Sderaadt  * On For each Tag, Add, Checkout, Commit, Update or Release command,
111e72d8d2Sderaadt  * one line of text is written to a History log.
121e72d8d2Sderaadt  *
131e72d8d2Sderaadt  *	X date | user | CurDir | special | rev(s) | argument '\n'
141e72d8d2Sderaadt  *
151e72d8d2Sderaadt  * where: [The spaces in the example line above are not in the history file.]
161e72d8d2Sderaadt  *
171e72d8d2Sderaadt  *  X		is a single character showing the type of event:
181e72d8d2Sderaadt  *		T	"Tag" cmd.
191e72d8d2Sderaadt  *		O	"Checkout" cmd.
20461cc63eStholo  *              E       "Export" cmd.
211e72d8d2Sderaadt  *		F	"Release" cmd.
221e72d8d2Sderaadt  *		W	"Update" cmd - No User file, Remove from Entries file.
231e72d8d2Sderaadt  *		U	"Update" cmd - File was checked out over User file.
241e72d8d2Sderaadt  *		G	"Update" cmd - File was merged successfully.
251e72d8d2Sderaadt  *		C	"Update" cmd - File was merged and shows overlaps.
261e72d8d2Sderaadt  *		M	"Commit" cmd - "Modified" file.
271e72d8d2Sderaadt  *		A	"Commit" cmd - "Added" file.
281e72d8d2Sderaadt  *		R	"Commit" cmd - "Removed" file.
291e72d8d2Sderaadt  *
301e72d8d2Sderaadt  *  date	is a fixed length 8-char hex representation of a Unix time_t.
311e72d8d2Sderaadt  *		[Starting here, variable fields are delimited by '|' chars.]
321e72d8d2Sderaadt  *
331e72d8d2Sderaadt  *  user	is the username of the person who typed the command.
341e72d8d2Sderaadt  *
351e72d8d2Sderaadt  *  CurDir	The directory where the action occurred.  This should be the
361e72d8d2Sderaadt  *		absolute path of the directory which is at the same level as
371e72d8d2Sderaadt  *		the "Repository" field (for W,U,G,C & M,A,R).
381e72d8d2Sderaadt  *
391e72d8d2Sderaadt  *  Repository	For record types [W,U,G,C,M,A,R] this field holds the
401e72d8d2Sderaadt  *		repository read from the administrative data where the
411e72d8d2Sderaadt  *		command was typed.
421e72d8d2Sderaadt  *		T	"A" --> New Tag, "D" --> Delete Tag
431e72d8d2Sderaadt  *			Otherwise it is the Tag or Date to modify.
44461cc63eStholo  *		O,F,E	A "" (null field)
451e72d8d2Sderaadt  *
461e72d8d2Sderaadt  *  rev(s)	Revision number or tag.
471e72d8d2Sderaadt  *		T	The Tag to apply.
48461cc63eStholo  *		O,E	The Tag or Date, if specified, else "" (null field).
491e72d8d2Sderaadt  *		F	"" (null field)
501e72d8d2Sderaadt  *		W	The Tag or Date, if specified, else "" (null field).
511e72d8d2Sderaadt  *		U	The Revision checked out over the User file.
521e72d8d2Sderaadt  *		G,C	The Revision(s) involved in merge.
531e72d8d2Sderaadt  *		M,A,R	RCS Revision affected.
541e72d8d2Sderaadt  *
55461cc63eStholo  *  argument	The module (for [TOEUF]) or file (for [WUGCMAR]) affected.
561e72d8d2Sderaadt  *
571e72d8d2Sderaadt  *
581e72d8d2Sderaadt  *** Report categories: "User" and "Since" modifiers apply to all reports.
591e72d8d2Sderaadt  *			[For "sort" ordering see the "sort_order" routine.]
601e72d8d2Sderaadt  *
611e72d8d2Sderaadt  *   Extract list of record types
621e72d8d2Sderaadt  *
63461cc63eStholo  *	-e, -x [TOEFWUGCMAR]
641e72d8d2Sderaadt  *
651e72d8d2Sderaadt  *		Extracted records are simply printed, No analysis is performed.
661e72d8d2Sderaadt  *		All "field" modifiers apply.  -e chooses all types.
671e72d8d2Sderaadt  *
681e72d8d2Sderaadt  *   Checked 'O'ut modules
691e72d8d2Sderaadt  *
701e72d8d2Sderaadt  *	-o, -w
711e72d8d2Sderaadt  *		Checked out modules.  'F' and 'O' records are examined and if
721e72d8d2Sderaadt  *		the last record for a repository/file is an 'O', a line is
731e72d8d2Sderaadt  *		printed.  "-w" forces the "working dir" to be used in the
741e72d8d2Sderaadt  *		comparison instead of the repository.
751e72d8d2Sderaadt  *
761e72d8d2Sderaadt  *   Committed (Modified) files
771e72d8d2Sderaadt  *
781e72d8d2Sderaadt  *	-c, -l, -w
791e72d8d2Sderaadt  *		All 'M'odified, 'A'dded and 'R'emoved records are examined.
801e72d8d2Sderaadt  *		"Field" modifiers apply.  -l forces a sort by file within user
811e72d8d2Sderaadt  *		and shows only the last modifier.  -w works as in Checkout.
821e72d8d2Sderaadt  *
831e72d8d2Sderaadt  *		Warning: Be careful with what you infer from the output of
841e72d8d2Sderaadt  *			 "cvs hi -c -l".  It means the last time *you*
851e72d8d2Sderaadt  *			 changed the file, not the list of files for which
861e72d8d2Sderaadt  *			 you were the last changer!!!
871e72d8d2Sderaadt  *
881e72d8d2Sderaadt  *   Module history for named modules.
891e72d8d2Sderaadt  *	-m module, -l
901e72d8d2Sderaadt  *
911e72d8d2Sderaadt  *		This is special.  If one or more modules are specified, the
921e72d8d2Sderaadt  *		module names are remembered and the files making up the
931e72d8d2Sderaadt  *		modules are remembered.  Only records matching exactly those
941e72d8d2Sderaadt  *		files and repositories are shown.  Sorting by "module", then
951e72d8d2Sderaadt  *		filename, is implied.  If -l ("last modified") is specified,
961e72d8d2Sderaadt  *		then "update" records (types WUCG), tag and release records
971e72d8d2Sderaadt  *		are ignored and the last (by date) "modified" record.
981e72d8d2Sderaadt  *
991e72d8d2Sderaadt  *   TAG history
1001e72d8d2Sderaadt  *
1011e72d8d2Sderaadt  *	-T	All Tag records are displayed.
1021e72d8d2Sderaadt  *
1031e72d8d2Sderaadt  *** Modifiers.
1041e72d8d2Sderaadt  *
1051e72d8d2Sderaadt  *   Since ...		[All records contain a timestamp, so any report
1061e72d8d2Sderaadt  *			 category can be limited by date.]
1071e72d8d2Sderaadt  *
1081e72d8d2Sderaadt  *	-D date		- The "date" is parsed into a Unix "time_t" and
1091e72d8d2Sderaadt  *			  records with an earlier time stamp are ignored.
1101e72d8d2Sderaadt  *	-r rev/tag	- A "rev" begins with a digit.  A "tag" does not.  If
1111e72d8d2Sderaadt  *			  you use this option, every file is searched for the
1121e72d8d2Sderaadt  *			  indicated rev/tag.
1131e72d8d2Sderaadt  *	-t tag		- The "tag" is searched for in the history file and no
1141e72d8d2Sderaadt  *			  record is displayed before the tag is found.  An
1151e72d8d2Sderaadt  *			  error is printed if the tag is never found.
1161e72d8d2Sderaadt  *	-b string	- Records are printed only back to the last reference
1171e72d8d2Sderaadt  *			  to the string in the "module", "file" or
1181e72d8d2Sderaadt  *			  "repository" fields.
1191e72d8d2Sderaadt  *
1201e72d8d2Sderaadt  *   Field Selections	[Simple comparisons on existing fields.  All field
1211e72d8d2Sderaadt  *			 selections are repeatable.]
1221e72d8d2Sderaadt  *
1231e72d8d2Sderaadt  *	-a		- All users.
1241e72d8d2Sderaadt  *	-u user		- If no user is given and '-a' is not given, only
1251e72d8d2Sderaadt  *			  records for the user typing the command are shown.
1261e72d8d2Sderaadt  *			  ==> If -a or -u is not specified, just use "self".
1271e72d8d2Sderaadt  *
1281e72d8d2Sderaadt  *	-f filematch	- Only records in which the "file" field contains the
1291e72d8d2Sderaadt  *			  string "filematch" are considered.
1301e72d8d2Sderaadt  *
1311e72d8d2Sderaadt  *	-p repository	- Only records in which the "repository" string is a
1321e72d8d2Sderaadt  *			  prefix of the "repos" field are considered.
1331e72d8d2Sderaadt  *
13443c1707eStholo  *	-n modulename	- Only records which contain "modulename" in the
1351e72d8d2Sderaadt  *			  "module" field are considered.
1361e72d8d2Sderaadt  *
1371e72d8d2Sderaadt  *
1381e72d8d2Sderaadt  * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
1391e72d8d2Sderaadt  *
1401e72d8d2Sderaadt  *** Checked out files for username.  (default self, e.g. "dgg")
1411e72d8d2Sderaadt  *	cvs hi			[equivalent to: "cvs hi -o -u dgg"]
1421e72d8d2Sderaadt  *	cvs hi -u user		[equivalent to: "cvs hi -o -u user"]
1431e72d8d2Sderaadt  *	cvs hi -o		[equivalent to: "cvs hi -o -u dgg"]
1441e72d8d2Sderaadt  *
1451e72d8d2Sderaadt  *** Committed (modified) files from the beginning of the file.
1461e72d8d2Sderaadt  *	cvs hi -c [-u user]
1471e72d8d2Sderaadt  *
1481e72d8d2Sderaadt  *** Committed (modified) files since Midnight, January 1, 1990:
1491e72d8d2Sderaadt  *	cvs hi -c -D 'Jan 1 1990' [-u user]
1501e72d8d2Sderaadt  *
1511e72d8d2Sderaadt  *** Committed (modified) files since tag "TAG" was stored in the history file:
1521e72d8d2Sderaadt  *	cvs hi -c -t TAG [-u user]
1531e72d8d2Sderaadt  *
1541e72d8d2Sderaadt  *** Committed (modified) files since tag "TAG" was placed on the files:
1551e72d8d2Sderaadt  *	cvs hi -c -r TAG [-u user]
1561e72d8d2Sderaadt  *
1571e72d8d2Sderaadt  *** Who last committed file/repository X?
1581e72d8d2Sderaadt  *	cvs hi -c -l -[fp] X
1591e72d8d2Sderaadt  *
1601e72d8d2Sderaadt  *** Modified files since tag/date/file/repos?
1611e72d8d2Sderaadt  *	cvs hi -c {-r TAG | -D Date | -b string}
1621e72d8d2Sderaadt  *
1631e72d8d2Sderaadt  *** Tag history
1641e72d8d2Sderaadt  *	cvs hi -T
1651e72d8d2Sderaadt  *
1661e72d8d2Sderaadt  *** History of file/repository/module X.
1671e72d8d2Sderaadt  *	cvs hi -[fpn] X
1681e72d8d2Sderaadt  *
1691e72d8d2Sderaadt  *** History of user "user".
1701e72d8d2Sderaadt  *	cvs hi -e -u user
1711e72d8d2Sderaadt  *
1721e72d8d2Sderaadt  *** Dump (eXtract) specified record types
1731e72d8d2Sderaadt  *	cvs hi -x [TOFWUGCMAR]
1741e72d8d2Sderaadt  *
1751e72d8d2Sderaadt  *
1761e72d8d2Sderaadt  * FUTURE:		J[Join], I[Import]  (Not currently implemented.)
1771e72d8d2Sderaadt  *
1781e72d8d2Sderaadt  */
1791e72d8d2Sderaadt 
1801e72d8d2Sderaadt #include "cvs.h"
181461cc63eStholo #include "savecwd.h"
1821e72d8d2Sderaadt 
1831e72d8d2Sderaadt static struct hrec
1841e72d8d2Sderaadt {
1851e72d8d2Sderaadt     char *type;		/* Type of record (In history record) */
1861e72d8d2Sderaadt     char *user;		/* Username (In history record) */
1871e72d8d2Sderaadt     char *dir;		/* "Compressed" Working dir (In history record) */
1881e72d8d2Sderaadt     char *repos;	/* (Tag is special.) Repository (In history record) */
1891e72d8d2Sderaadt     char *rev;		/* Revision affected (In history record) */
1901e72d8d2Sderaadt     char *file;		/* Filename (In history record) */
1911e72d8d2Sderaadt     char *end;		/* Ptr into repository to copy at end of workdir */
1921e72d8d2Sderaadt     char *mod;		/* The module within which the file is contained */
1931e72d8d2Sderaadt     time_t date;	/* Calculated from date stored in record */
19443c1707eStholo     long idx;		/* Index of record, for "stable" sort. */
1951e72d8d2Sderaadt } *hrec_head;
19643c1707eStholo static long hrec_idx;
1971e72d8d2Sderaadt 
1981e72d8d2Sderaadt 
199e77048c1Stholo static void fill_hrec PROTO((char *line, struct hrec * hr));
2001e72d8d2Sderaadt static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
2011e72d8d2Sderaadt static int select_hrec PROTO((struct hrec * hr));
2021e72d8d2Sderaadt static int sort_order PROTO((const PTR l, const PTR r));
2031e72d8d2Sderaadt static int within PROTO((char *find, char *string));
2041e72d8d2Sderaadt static void expand_modules PROTO((void));
2051e72d8d2Sderaadt static void read_hrecs PROTO((char *fname));
2061e72d8d2Sderaadt static void report_hrecs PROTO((void));
2071e72d8d2Sderaadt static void save_file PROTO((char *dir, char *name, char *module));
2081e72d8d2Sderaadt static void save_module PROTO((char *module));
2091e72d8d2Sderaadt static void save_user PROTO((char *name));
2101e72d8d2Sderaadt 
211461cc63eStholo #define ALL_REC_TYPES "TOEFWUCGMAR"
2121e72d8d2Sderaadt #define USER_INCREMENT	2
2131e72d8d2Sderaadt #define FILE_INCREMENT	128
2141e72d8d2Sderaadt #define MODULE_INCREMENT 5
2151e72d8d2Sderaadt #define HREC_INCREMENT	128
2161e72d8d2Sderaadt 
2171e72d8d2Sderaadt static short report_count;
2181e72d8d2Sderaadt 
2191e72d8d2Sderaadt static short extract;
2201e72d8d2Sderaadt static short v_checkout;
2211e72d8d2Sderaadt static short modified;
2221e72d8d2Sderaadt static short tag_report;
2231e72d8d2Sderaadt static short module_report;
2241e72d8d2Sderaadt static short working;
2251e72d8d2Sderaadt static short last_entry;
2261e72d8d2Sderaadt static short all_users;
2271e72d8d2Sderaadt 
2281e72d8d2Sderaadt static short user_sort;
2291e72d8d2Sderaadt static short repos_sort;
2301e72d8d2Sderaadt static short file_sort;
2311e72d8d2Sderaadt static short module_sort;
2321e72d8d2Sderaadt 
2331e72d8d2Sderaadt static short tz_local;
2341e72d8d2Sderaadt static time_t tz_seconds_east_of_GMT;
2351e72d8d2Sderaadt static char *tz_name = "+0000";
2361e72d8d2Sderaadt 
237e77048c1Stholo char *logHistory = ALL_REC_TYPES;
238e77048c1Stholo 
239780d15dfStholo /* -r, -t, or -b options, malloc'd.  These are "" if the option in
240780d15dfStholo    question is not specified or is overridden by another option.  The
241780d15dfStholo    main reason for using "" rather than NULL is historical.  Together
242780d15dfStholo    with since_date, these are a mutually exclusive set; one overrides the
243780d15dfStholo    others.  */
244780d15dfStholo static char *since_rev;
245780d15dfStholo static char *since_tag;
246780d15dfStholo static char *backto;
247b6f6614eStholo /* -D option, or 0 if not specified.  RCS format.  */
248b6f6614eStholo static char * since_date;
249780d15dfStholo 
2501e72d8d2Sderaadt static struct hrec *last_since_tag;
2511e72d8d2Sderaadt static struct hrec *last_backto;
252780d15dfStholo 
253780d15dfStholo /* Record types to look for, malloc'd.  Probably could be statically
254780d15dfStholo    allocated, but only if we wanted to check for duplicates more than
255780d15dfStholo    we do.  */
256780d15dfStholo static char *rec_types;
2571e72d8d2Sderaadt 
2581e72d8d2Sderaadt static int hrec_count;
2591e72d8d2Sderaadt static int hrec_max;
2601e72d8d2Sderaadt 
2611e72d8d2Sderaadt static char **user_list;	/* Ptr to array of ptrs to user names */
2621e72d8d2Sderaadt static int user_max;		/* Number of elements allocated */
2631e72d8d2Sderaadt static int user_count;		/* Number of elements used */
2641e72d8d2Sderaadt 
2651e72d8d2Sderaadt static struct file_list_str
2661e72d8d2Sderaadt {
2671e72d8d2Sderaadt     char *l_file;
2681e72d8d2Sderaadt     char *l_module;
2691e72d8d2Sderaadt } *file_list;			/* Ptr to array file name structs */
2701e72d8d2Sderaadt static int file_max;		/* Number of elements allocated */
2711e72d8d2Sderaadt static int file_count;		/* Number of elements used */
2721e72d8d2Sderaadt 
2731e72d8d2Sderaadt static char **mod_list;		/* Ptr to array of ptrs to module names */
2741e72d8d2Sderaadt static int mod_max;		/* Number of elements allocated */
2751e72d8d2Sderaadt static int mod_count;		/* Number of elements used */
2761e72d8d2Sderaadt 
2771e72d8d2Sderaadt static char *histfile;		/* Ptr to the history file name */
2781e72d8d2Sderaadt 
2792770ece5Stholo /* This is pretty unclear.  First of all, separating "flags" vs.
2802770ece5Stholo    "options" (I think the distinction is that "options" take arguments)
2812770ece5Stholo    is nonstandard, and not something we do elsewhere in CVS.  Second of
2822770ece5Stholo    all, what does "reports" mean?  I think it means that you can only
2832770ece5Stholo    supply one of those options, but "reports" hardly has that meaning in
2842770ece5Stholo    a self-explanatory way.  */
2851e72d8d2Sderaadt static const char *const history_usg[] =
2861e72d8d2Sderaadt {
2871e72d8d2Sderaadt     "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
2881e72d8d2Sderaadt     "   Reports:\n",
2891e72d8d2Sderaadt     "        -T              Produce report on all TAGs\n",
2901e72d8d2Sderaadt     "        -c              Committed (Modified) files\n",
2911e72d8d2Sderaadt     "        -o              Checked out modules\n",
2921e72d8d2Sderaadt     "        -m <module>     Look for specified module (repeatable)\n",
293461cc63eStholo     "        -x [TOEFWUCGMAR] Extract by record type\n",
294e77048c1Stholo     "        -e              Everything (same as -x, but all record types)\n",
2951e72d8d2Sderaadt     "   Flags:\n",
2961e72d8d2Sderaadt     "        -a              All users (Default is self)\n",
2971e72d8d2Sderaadt     "        -l              Last modified (committed or modified report)\n",
2981e72d8d2Sderaadt     "        -w              Working directory must match\n",
2991e72d8d2Sderaadt     "   Options:\n",
3001e72d8d2Sderaadt     "        -D <date>       Since date (Many formats)\n",
3011e72d8d2Sderaadt     "        -b <str>        Back to record with str in module/file/repos field\n",
3021e72d8d2Sderaadt     "        -f <file>       Specified file (same as command line) (repeatable)\n",
3031e72d8d2Sderaadt     "        -n <modulename> In module (repeatable)\n",
3041e72d8d2Sderaadt     "        -p <repos>      In repository (repeatable)\n",
3051e72d8d2Sderaadt     "        -r <rev/tag>    Since rev or tag (looks inside RCS files!)\n",
3061e72d8d2Sderaadt     "        -t <tag>        Since tag record placed in history file (by anyone).\n",
3071e72d8d2Sderaadt     "        -u <user>       For user name (repeatable)\n",
3081e72d8d2Sderaadt     "        -z <tz>         Output for time zone <tz> (e.g. -z -0700)\n",
3091e72d8d2Sderaadt     NULL};
3101e72d8d2Sderaadt 
3111e72d8d2Sderaadt /* Sort routine for qsort:
3121e72d8d2Sderaadt    - If a user is selected at all, sort it first. User-within-file is useless.
3131e72d8d2Sderaadt    - If a module was selected explicitly, sort next on module.
3141e72d8d2Sderaadt    - Then sort by file.  "File" is "repository/file" unless "working" is set,
3151e72d8d2Sderaadt      then it is "workdir/file".  (Revision order should always track date.)
3161e72d8d2Sderaadt    - Always sort timestamp last.
3171e72d8d2Sderaadt */
3181e72d8d2Sderaadt static int
sort_order(l,r)3191e72d8d2Sderaadt sort_order (l, r)
3201e72d8d2Sderaadt     const PTR l;
3211e72d8d2Sderaadt     const PTR r;
3221e72d8d2Sderaadt {
3231e72d8d2Sderaadt     int i;
3241e72d8d2Sderaadt     const struct hrec *left = (const struct hrec *) l;
3251e72d8d2Sderaadt     const struct hrec *right = (const struct hrec *) r;
3261e72d8d2Sderaadt 
3271e72d8d2Sderaadt     if (user_sort)	/* If Sort by username, compare users */
3281e72d8d2Sderaadt     {
3291e72d8d2Sderaadt 	if ((i = strcmp (left->user, right->user)) != 0)
3301e72d8d2Sderaadt 	    return (i);
3311e72d8d2Sderaadt     }
3321e72d8d2Sderaadt     if (module_sort)	/* If sort by modules, compare module names */
3331e72d8d2Sderaadt     {
3341e72d8d2Sderaadt 	if (left->mod && right->mod)
3351e72d8d2Sderaadt 	    if ((i = strcmp (left->mod, right->mod)) != 0)
3361e72d8d2Sderaadt 		return (i);
3371e72d8d2Sderaadt     }
3381e72d8d2Sderaadt     if (repos_sort)	/* If sort by repository, compare them. */
3391e72d8d2Sderaadt     {
3401e72d8d2Sderaadt 	if ((i = strcmp (left->repos, right->repos)) != 0)
3411e72d8d2Sderaadt 	    return (i);
3421e72d8d2Sderaadt     }
3431e72d8d2Sderaadt     if (file_sort)	/* If sort by filename, compare files, NOT dirs. */
3441e72d8d2Sderaadt     {
3451e72d8d2Sderaadt 	if ((i = strcmp (left->file, right->file)) != 0)
3461e72d8d2Sderaadt 	    return (i);
3471e72d8d2Sderaadt 
3481e72d8d2Sderaadt 	if (working)
3491e72d8d2Sderaadt 	{
3501e72d8d2Sderaadt 	    if ((i = strcmp (left->dir, right->dir)) != 0)
3511e72d8d2Sderaadt 		return (i);
3521e72d8d2Sderaadt 
3531e72d8d2Sderaadt 	    if ((i = strcmp (left->end, right->end)) != 0)
3541e72d8d2Sderaadt 		return (i);
3551e72d8d2Sderaadt 	}
3561e72d8d2Sderaadt     }
3571e72d8d2Sderaadt 
3581e72d8d2Sderaadt     /*
3591e72d8d2Sderaadt      * By default, sort by date, time
3601e72d8d2Sderaadt      * XXX: This fails after 2030 when date slides into sign bit
3611e72d8d2Sderaadt      */
3621e72d8d2Sderaadt     if ((i = ((long) (left->date) - (long) (right->date))) != 0)
3631e72d8d2Sderaadt 	return (i);
3641e72d8d2Sderaadt 
3651e72d8d2Sderaadt     /* For matching dates, keep the sort stable by using record index */
3661e72d8d2Sderaadt     return (left->idx - right->idx);
3671e72d8d2Sderaadt }
3681e72d8d2Sderaadt 
3691e72d8d2Sderaadt int
history(argc,argv)3701e72d8d2Sderaadt history (argc, argv)
3711e72d8d2Sderaadt     int argc;
3721e72d8d2Sderaadt     char **argv;
3731e72d8d2Sderaadt {
3741e72d8d2Sderaadt     int i, c;
375461cc63eStholo     char *fname;
3761e72d8d2Sderaadt 
3771e72d8d2Sderaadt     if (argc == -1)
3781e72d8d2Sderaadt 	usage (history_usg);
3791e72d8d2Sderaadt 
380780d15dfStholo     since_rev = xstrdup ("");
381780d15dfStholo     since_tag = xstrdup ("");
382780d15dfStholo     backto = xstrdup ("");
383780d15dfStholo     rec_types = xstrdup ("");
3842770ece5Stholo     optind = 0;
385b6c02222Stholo     while ((c = getopt (argc, argv, "+Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
3861e72d8d2Sderaadt     {
3871e72d8d2Sderaadt 	switch (c)
3881e72d8d2Sderaadt 	{
3891e72d8d2Sderaadt 	    case 'T':			/* Tag list */
3901e72d8d2Sderaadt 		report_count++;
3911e72d8d2Sderaadt 		tag_report++;
3921e72d8d2Sderaadt 		break;
3931e72d8d2Sderaadt 	    case 'a':			/* For all usernames */
3941e72d8d2Sderaadt 		all_users++;
3951e72d8d2Sderaadt 		break;
3961e72d8d2Sderaadt 	    case 'c':
3971e72d8d2Sderaadt 		report_count++;
3981e72d8d2Sderaadt 		modified = 1;
3991e72d8d2Sderaadt 		break;
4001e72d8d2Sderaadt 	    case 'e':
4011e72d8d2Sderaadt 		report_count++;
4021e72d8d2Sderaadt 		extract++;
403780d15dfStholo 		free (rec_types);
404780d15dfStholo 		rec_types = xstrdup (ALL_REC_TYPES);
4051e72d8d2Sderaadt 		break;
4061e72d8d2Sderaadt 	    case 'l':			/* Find Last file record */
4071e72d8d2Sderaadt 		last_entry = 1;
4081e72d8d2Sderaadt 		break;
4091e72d8d2Sderaadt 	    case 'o':
4101e72d8d2Sderaadt 		report_count++;
4111e72d8d2Sderaadt 		v_checkout = 1;
4121e72d8d2Sderaadt 		break;
4131e72d8d2Sderaadt 	    case 'w':			/* Match Working Dir (CurDir) fields */
4141e72d8d2Sderaadt 		working = 1;
4151e72d8d2Sderaadt 		break;
4161e72d8d2Sderaadt 	    case 'X':			/* Undocumented debugging flag */
4172ddaa231Sotto #ifdef DEBUG
4181e72d8d2Sderaadt 		histfile = optarg;
4192ddaa231Sotto #endif
4201e72d8d2Sderaadt 		break;
4212ddaa231Sotto 
4221e72d8d2Sderaadt 	    case 'D':			/* Since specified date */
4231e72d8d2Sderaadt 		if (*since_rev || *since_tag || *backto)
4241e72d8d2Sderaadt 		{
4251e72d8d2Sderaadt 		    error (0, 0, "date overriding rev/tag/backto");
4261e72d8d2Sderaadt 		    *since_rev = *since_tag = *backto = '\0';
4271e72d8d2Sderaadt 		}
428b6f6614eStholo 		since_date = Make_Date (optarg);
4291e72d8d2Sderaadt 		break;
4301e72d8d2Sderaadt 	    case 'b':			/* Since specified file/Repos */
4311e72d8d2Sderaadt 		if (since_date || *since_rev || *since_tag)
4321e72d8d2Sderaadt 		{
4331e72d8d2Sderaadt 		    error (0, 0, "backto overriding date/rev/tag");
4341e72d8d2Sderaadt 		    *since_rev = *since_tag = '\0';
435b6f6614eStholo 		    if (since_date != NULL)
436b6f6614eStholo 			free (since_date);
437b6f6614eStholo 		    since_date = NULL;
4381e72d8d2Sderaadt 		}
439780d15dfStholo 		free (backto);
440780d15dfStholo 		backto = xstrdup (optarg);
4411e72d8d2Sderaadt 		break;
4421e72d8d2Sderaadt 	    case 'f':			/* For specified file */
4431e72d8d2Sderaadt 		save_file ("", optarg, (char *) NULL);
4441e72d8d2Sderaadt 		break;
4451e72d8d2Sderaadt 	    case 'm':			/* Full module report */
44643c1707eStholo 		if (!module_report++) report_count++;
44743c1707eStholo 		/* fall through */
4481e72d8d2Sderaadt 	    case 'n':			/* Look for specified module */
4491e72d8d2Sderaadt 		save_module (optarg);
4501e72d8d2Sderaadt 		break;
4511e72d8d2Sderaadt 	    case 'p':			/* For specified directory */
4521e72d8d2Sderaadt 		save_file (optarg, "", (char *) NULL);
4531e72d8d2Sderaadt 		break;
4541e72d8d2Sderaadt 	    case 'r':			/* Since specified Tag/Rev */
4551e72d8d2Sderaadt 		if (since_date || *since_tag || *backto)
4561e72d8d2Sderaadt 		{
4571e72d8d2Sderaadt 		    error (0, 0, "rev overriding date/tag/backto");
4581e72d8d2Sderaadt 		    *since_tag = *backto = '\0';
459b6f6614eStholo 		    if (since_date != NULL)
460b6f6614eStholo 			free (since_date);
461b6f6614eStholo 		    since_date = NULL;
4621e72d8d2Sderaadt 		}
463780d15dfStholo 		free (since_rev);
464780d15dfStholo 		since_rev = xstrdup (optarg);
4651e72d8d2Sderaadt 		break;
4661e72d8d2Sderaadt 	    case 't':			/* Since specified Tag/Rev */
4671e72d8d2Sderaadt 		if (since_date || *since_rev || *backto)
4681e72d8d2Sderaadt 		{
4691e72d8d2Sderaadt 		    error (0, 0, "tag overriding date/marker/file/repos");
4701e72d8d2Sderaadt 		    *since_rev = *backto = '\0';
471b6f6614eStholo 		    if (since_date != NULL)
472b6f6614eStholo 			free (since_date);
473b6f6614eStholo 		    since_date = NULL;
4741e72d8d2Sderaadt 		}
475780d15dfStholo 		free (since_tag);
476780d15dfStholo 		since_tag = xstrdup (optarg);
4771e72d8d2Sderaadt 		break;
4781e72d8d2Sderaadt 	    case 'u':			/* For specified username */
4791e72d8d2Sderaadt 		save_user (optarg);
4801e72d8d2Sderaadt 		break;
4811e72d8d2Sderaadt 	    case 'x':
4821e72d8d2Sderaadt 		report_count++;
4831e72d8d2Sderaadt 		extract++;
4841e72d8d2Sderaadt 		{
4851e72d8d2Sderaadt 		    char *cp;
4861e72d8d2Sderaadt 
4871e72d8d2Sderaadt 		    for (cp = optarg; *cp; cp++)
4881e72d8d2Sderaadt 			if (!strchr (ALL_REC_TYPES, *cp))
4891e72d8d2Sderaadt 			    error (1, 0, "%c is not a valid report type", *cp);
4901e72d8d2Sderaadt 		}
491780d15dfStholo 		free (rec_types);
492780d15dfStholo 		rec_types = xstrdup (optarg);
4931e72d8d2Sderaadt 		break;
4941e72d8d2Sderaadt 	    case 'z':
4951e72d8d2Sderaadt 		tz_local =
4961e72d8d2Sderaadt 		    (optarg[0] == 'l' || optarg[0] == 'L')
4971e72d8d2Sderaadt 		    && (optarg[1] == 't' || optarg[1] == 'T')
4981e72d8d2Sderaadt 		    && !optarg[2];
4991e72d8d2Sderaadt 		if (tz_local)
5001e72d8d2Sderaadt 		    tz_name = optarg;
5011e72d8d2Sderaadt 		else
5021e72d8d2Sderaadt 		{
5031e72d8d2Sderaadt 		    /*
5041e72d8d2Sderaadt 		     * Convert a known time with the given timezone to time_t.
5051e72d8d2Sderaadt 		     * Use the epoch + 23 hours, so timezones east of GMT work.
5061e72d8d2Sderaadt 		     */
5071e72d8d2Sderaadt 		    static char f[] = "1/1/1970 23:00 %s";
5081e72d8d2Sderaadt 		    char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
5091e72d8d2Sderaadt 		    time_t t;
5101e72d8d2Sderaadt 		    sprintf (buf, f, optarg);
511e79e3062Smillert 		    t = get_date (buf);
5121e72d8d2Sderaadt 		    free (buf);
5131e72d8d2Sderaadt 		    if (t == (time_t) -1)
5141e72d8d2Sderaadt 			error (0, 0, "%s is not a known time zone", optarg);
5151e72d8d2Sderaadt 		    else
5161e72d8d2Sderaadt 		    {
5171e72d8d2Sderaadt 			/*
5181e72d8d2Sderaadt 			 * Convert to seconds east of GMT, removing the
5191e72d8d2Sderaadt 			 * 23-hour offset mentioned above.
5201e72d8d2Sderaadt 			 */
5211e72d8d2Sderaadt 			tz_seconds_east_of_GMT = (time_t)23 * 60 * 60  -  t;
5221e72d8d2Sderaadt 			tz_name = optarg;
5231e72d8d2Sderaadt 		    }
5241e72d8d2Sderaadt 		}
5251e72d8d2Sderaadt 		break;
5261e72d8d2Sderaadt 	    case '?':
5271e72d8d2Sderaadt 	    default:
5281e72d8d2Sderaadt 		usage (history_usg);
5291e72d8d2Sderaadt 		break;
5301e72d8d2Sderaadt 	}
5311e72d8d2Sderaadt     }
532e77048c1Stholo     argc -= optind;
533e77048c1Stholo     argv += optind;
534e77048c1Stholo     for (i = 0; i < argc; i++)
535e77048c1Stholo 	save_file ("", argv[i], (char *) NULL);
536e77048c1Stholo 
5371e72d8d2Sderaadt 
5381e72d8d2Sderaadt     /* ================ Now analyze the arguments a bit */
5391e72d8d2Sderaadt     if (!report_count)
5401e72d8d2Sderaadt 	v_checkout++;
5411e72d8d2Sderaadt     else if (report_count > 1)
542e77048c1Stholo 	error (1, 0, "Only one report type allowed from: \"-Tcomxe\".");
5431e72d8d2Sderaadt 
5441e72d8d2Sderaadt #ifdef CLIENT_SUPPORT
54543c1707eStholo     if (current_parsed_root->isremote)
5461e72d8d2Sderaadt     {
5471e72d8d2Sderaadt 	struct file_list_str *f1;
5481e72d8d2Sderaadt 	char **mod;
5491e72d8d2Sderaadt 
5501e72d8d2Sderaadt 	/* We're the client side.  Fire up the remote server.  */
5511e72d8d2Sderaadt 	start_server ();
5521e72d8d2Sderaadt 
5531e72d8d2Sderaadt 	ign_setup ();
5541e72d8d2Sderaadt 
5551e72d8d2Sderaadt 	if (tag_report)
5561e72d8d2Sderaadt 	    send_arg("-T");
5571e72d8d2Sderaadt 	if (all_users)
5581e72d8d2Sderaadt 	    send_arg("-a");
5591e72d8d2Sderaadt 	if (modified)
5601e72d8d2Sderaadt 	    send_arg("-c");
5611e72d8d2Sderaadt 	if (last_entry)
5621e72d8d2Sderaadt 	    send_arg("-l");
5631e72d8d2Sderaadt 	if (v_checkout)
5641e72d8d2Sderaadt 	    send_arg("-o");
5651e72d8d2Sderaadt 	if (working)
5661e72d8d2Sderaadt 	    send_arg("-w");
5671e72d8d2Sderaadt 	if (histfile)
5681e72d8d2Sderaadt 	    send_arg("-X");
5691e72d8d2Sderaadt 	if (since_date)
570b6f6614eStholo 	    client_senddate (since_date);
5711e72d8d2Sderaadt 	if (backto[0] != '\0')
5721e72d8d2Sderaadt 	    option_with_arg ("-b", backto);
5731e72d8d2Sderaadt 	for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
5741e72d8d2Sderaadt 	{
5751e72d8d2Sderaadt 	    if (f1->l_file[0] == '*')
5761e72d8d2Sderaadt 		option_with_arg ("-p", f1->l_file + 1);
5771e72d8d2Sderaadt 	    else
5781e72d8d2Sderaadt 		option_with_arg ("-f", f1->l_file);
5791e72d8d2Sderaadt 	}
5801e72d8d2Sderaadt 	if (module_report)
5811e72d8d2Sderaadt 	    send_arg("-m");
5821e72d8d2Sderaadt 	for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
5831e72d8d2Sderaadt 	    option_with_arg ("-n", *mod);
58450bf276cStholo 	if (*since_rev)
5851e72d8d2Sderaadt 	    option_with_arg ("-r", since_rev);
58650bf276cStholo 	if (*since_tag)
5871e72d8d2Sderaadt 	    option_with_arg ("-t", since_tag);
5881e72d8d2Sderaadt 	for (mod = user_list; mod < &user_list[user_count]; ++mod)
5891e72d8d2Sderaadt 	    option_with_arg ("-u", *mod);
5901e72d8d2Sderaadt 	if (extract)
5911e72d8d2Sderaadt 	    option_with_arg ("-x", rec_types);
5921e72d8d2Sderaadt 	option_with_arg ("-z", tz_name);
5931e72d8d2Sderaadt 
59413571821Stholo 	send_to_server ("history\012", 0);
5951e72d8d2Sderaadt         return get_responses_and_close ();
5961e72d8d2Sderaadt     }
5971e72d8d2Sderaadt #endif
5981e72d8d2Sderaadt 
5991e72d8d2Sderaadt     if (all_users)
6001e72d8d2Sderaadt 	save_user ("");
6011e72d8d2Sderaadt 
6021e72d8d2Sderaadt     if (mod_list)
6031e72d8d2Sderaadt 	expand_modules ();
6041e72d8d2Sderaadt 
6051e72d8d2Sderaadt     if (tag_report)
6061e72d8d2Sderaadt     {
6071e72d8d2Sderaadt 	if (!strchr (rec_types, 'T'))
608780d15dfStholo 	{
609780d15dfStholo 	    rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
6101e72d8d2Sderaadt 	    (void) strcat (rec_types, "T");
6111e72d8d2Sderaadt 	}
612780d15dfStholo     }
6131e72d8d2Sderaadt     else if (extract)
6141e72d8d2Sderaadt     {
6151e72d8d2Sderaadt 	if (user_list)
6161e72d8d2Sderaadt 	    user_sort++;
6171e72d8d2Sderaadt     }
6181e72d8d2Sderaadt     else if (modified)
6191e72d8d2Sderaadt     {
620780d15dfStholo 	free (rec_types);
621780d15dfStholo 	rec_types = xstrdup ("MAR");
6221e72d8d2Sderaadt 	/*
6231e72d8d2Sderaadt 	 * If the user has not specified a date oriented flag ("Since"), sort
6241e72d8d2Sderaadt 	 * by Repository/file before date.  Default is "just" date.
6251e72d8d2Sderaadt 	 */
626e77048c1Stholo 	if (last_entry
627e77048c1Stholo 	    || (!since_date && !*since_rev && !*since_tag && !*backto))
6281e72d8d2Sderaadt 	{
6291e72d8d2Sderaadt 	    repos_sort++;
6301e72d8d2Sderaadt 	    file_sort++;
6311e72d8d2Sderaadt 	    /*
6321e72d8d2Sderaadt 	     * If we are not looking for last_modified and the user specified
6331e72d8d2Sderaadt 	     * one or more users to look at, sort by user before filename.
6341e72d8d2Sderaadt 	     */
6351e72d8d2Sderaadt 	    if (!last_entry && user_list)
6361e72d8d2Sderaadt 		user_sort++;
6371e72d8d2Sderaadt 	}
6381e72d8d2Sderaadt     }
6391e72d8d2Sderaadt     else if (module_report)
6401e72d8d2Sderaadt     {
641780d15dfStholo 	free (rec_types);
642780d15dfStholo 	rec_types = xstrdup (last_entry ? "OMAR" : ALL_REC_TYPES);
6431e72d8d2Sderaadt 	module_sort++;
6441e72d8d2Sderaadt 	repos_sort++;
6451e72d8d2Sderaadt 	file_sort++;
6461e72d8d2Sderaadt 	working = 0;			/* User's workdir doesn't count here */
6471e72d8d2Sderaadt     }
6481e72d8d2Sderaadt     else
6491e72d8d2Sderaadt 	/* Must be "checkout" or default */
6501e72d8d2Sderaadt     {
651780d15dfStholo 	free (rec_types);
652780d15dfStholo 	rec_types = xstrdup ("OF");
6531e72d8d2Sderaadt 	/* See comments in "modified" above */
6541e72d8d2Sderaadt 	if (!last_entry && user_list)
6551e72d8d2Sderaadt 	    user_sort++;
656e77048c1Stholo 	if (last_entry
657e77048c1Stholo 	    || (!since_date && !*since_rev && !*since_tag && !*backto))
6581e72d8d2Sderaadt 	    file_sort++;
6591e72d8d2Sderaadt     }
6601e72d8d2Sderaadt 
6611e72d8d2Sderaadt     /* If no users were specified, use self (-a saves a universal ("") user) */
6621e72d8d2Sderaadt     if (!user_list)
6631e72d8d2Sderaadt 	save_user (getcaller ());
6641e72d8d2Sderaadt 
6651e72d8d2Sderaadt     /* If we're looking back to a Tag value, must consider "Tag" records */
6661e72d8d2Sderaadt     if (*since_tag && !strchr (rec_types, 'T'))
667780d15dfStholo     {
668780d15dfStholo 	rec_types = xrealloc (rec_types, strlen (rec_types) + 5);
6691e72d8d2Sderaadt 	(void) strcat (rec_types, "T");
670780d15dfStholo     }
6711e72d8d2Sderaadt 
6721e72d8d2Sderaadt     if (histfile)
673461cc63eStholo 	fname = xstrdup (histfile);
6741e72d8d2Sderaadt     else
675461cc63eStholo     {
67643c1707eStholo 	fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
677461cc63eStholo 			 + sizeof (CVSROOTADM_HISTORY) + 10);
67843c1707eStholo 	(void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
6791e72d8d2Sderaadt 			CVSROOTADM, CVSROOTADM_HISTORY);
680461cc63eStholo     }
6811e72d8d2Sderaadt 
6821e72d8d2Sderaadt     read_hrecs (fname);
683e77048c1Stholo     if(hrec_count>0)
684e77048c1Stholo     {
685e77048c1Stholo 	qsort ((PTR) hrec_head, hrec_count,
686e77048c1Stholo 		sizeof (struct hrec), sort_order);
687e77048c1Stholo     }
6881e72d8d2Sderaadt     report_hrecs ();
689461cc63eStholo     free (fname);
690b6f6614eStholo     if (since_date != NULL)
691b6f6614eStholo 	free (since_date);
692780d15dfStholo     free (since_rev);
693780d15dfStholo     free (since_tag);
694780d15dfStholo     free (backto);
695780d15dfStholo     free (rec_types);
6961e72d8d2Sderaadt 
6971e72d8d2Sderaadt     return (0);
6981e72d8d2Sderaadt }
6991e72d8d2Sderaadt 
7001e72d8d2Sderaadt void
history_write(type,update_dir,revs,name,repository)7011e72d8d2Sderaadt history_write (type, update_dir, revs, name, repository)
7021e72d8d2Sderaadt     int type;
7031e72d8d2Sderaadt     char *update_dir;
7041e72d8d2Sderaadt     char *revs;
7051e72d8d2Sderaadt     char *name;
7061e72d8d2Sderaadt     char *repository;
7071e72d8d2Sderaadt {
708461cc63eStholo     char *fname;
709461cc63eStholo     char *workdir;
710c26070a5Stholo     char *username = getcaller ();
7111e72d8d2Sderaadt     int fd;
7121e72d8d2Sderaadt     char *line;
7131e72d8d2Sderaadt     char *slash = "", *cp, *cp2, *repos;
7141e72d8d2Sderaadt     int i;
7151e72d8d2Sderaadt     static char *tilde = "";
7161e72d8d2Sderaadt     static char *PrCurDir = NULL;
7171e72d8d2Sderaadt 
7181e72d8d2Sderaadt     if (logoff)			/* History is turned off by cmd line switch */
7191e72d8d2Sderaadt 	return;
720e77048c1Stholo     if ( strchr(logHistory, type) == NULL )
721e77048c1Stholo 	return;
72243c1707eStholo     fname = xmalloc (strlen (current_parsed_root->directory) + sizeof (CVSROOTADM)
72343c1707eStholo 		     + sizeof (CVSROOTADM_HISTORY) + 3);
72443c1707eStholo     (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
72550bf276cStholo 		    CVSROOTADM, CVSROOTADM_HISTORY);
7261e72d8d2Sderaadt 
7271e72d8d2Sderaadt     /* turn off history logging if the history file does not exist */
7281e72d8d2Sderaadt     if (!isfile (fname))
7291e72d8d2Sderaadt     {
7301e72d8d2Sderaadt 	logoff = 1;
731461cc63eStholo 	goto out;
7321e72d8d2Sderaadt     }
7331e72d8d2Sderaadt 
7341e72d8d2Sderaadt     if (trace)
735c71bc7e2Stholo 	fprintf (stderr, "%s-> fopen(%s,a)\n",
736c71bc7e2Stholo 		 CLIENT_SERVER_STR, fname);
7371e72d8d2Sderaadt     if (noexec)
738461cc63eStholo 	goto out;
73950bf276cStholo     fd = CVS_OPEN (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666);
740c26070a5Stholo     if (fd < 0)
741e77048c1Stholo     {
742e77048c1Stholo 	if (! really_quiet)
743e77048c1Stholo         {
744e77048c1Stholo             error (0, errno, "warning: cannot write to history file %s",
745e77048c1Stholo                    fname);
746e77048c1Stholo         }
747e77048c1Stholo         goto out;
748e77048c1Stholo     }
7491e72d8d2Sderaadt 
7501e72d8d2Sderaadt     repos = Short_Repository (repository);
7511e72d8d2Sderaadt 
7521e72d8d2Sderaadt     if (!PrCurDir)
7531e72d8d2Sderaadt     {
754c26070a5Stholo 	char *pwdir;
7551e72d8d2Sderaadt 
756c26070a5Stholo 	pwdir = get_homedir ();
7571e72d8d2Sderaadt 	PrCurDir = CurDir;
758c26070a5Stholo 	if (pwdir != NULL)
7591e72d8d2Sderaadt 	{
760c26070a5Stholo 	    /* Assumes neither CurDir nor pwdir ends in '/' */
761c26070a5Stholo 	    i = strlen (pwdir);
762c26070a5Stholo 	    if (!strncmp (CurDir, pwdir, i))
7631e72d8d2Sderaadt 	    {
7641e72d8d2Sderaadt 		PrCurDir += i;		/* Point to '/' separator */
7651e72d8d2Sderaadt 		tilde = "~";
7661e72d8d2Sderaadt 	    }
7671e72d8d2Sderaadt 	    else
7681e72d8d2Sderaadt 	    {
7691e72d8d2Sderaadt 		/* Try harder to find a "homedir" */
770461cc63eStholo 		struct saved_cwd cwd;
771461cc63eStholo 		char *homedir;
772461cc63eStholo 
773461cc63eStholo 		if (save_cwd (&cwd))
774461cc63eStholo 		    error_exit ();
775461cc63eStholo 
77650bf276cStholo 		if ( CVS_CHDIR (pwdir) < 0)
777c26070a5Stholo 		    error (1, errno, "can't chdir(%s)", pwdir);
778461cc63eStholo 		homedir = xgetwd ();
779461cc63eStholo 		if (homedir == NULL)
780c26070a5Stholo 		    error (1, errno, "can't getwd in %s", pwdir);
781461cc63eStholo 
782461cc63eStholo 		if (restore_cwd (&cwd, NULL))
783461cc63eStholo 		    error_exit ();
784461cc63eStholo 		free_cwd (&cwd);
7851e72d8d2Sderaadt 
7861e72d8d2Sderaadt 		i = strlen (homedir);
7871e72d8d2Sderaadt 		if (!strncmp (CurDir, homedir, i))
7881e72d8d2Sderaadt 		{
7891e72d8d2Sderaadt 		    PrCurDir += i;	/* Point to '/' separator */
7901e72d8d2Sderaadt 		    tilde = "~";
7911e72d8d2Sderaadt 		}
792461cc63eStholo 		free (homedir);
7931e72d8d2Sderaadt 	    }
7941e72d8d2Sderaadt 	}
7951e72d8d2Sderaadt     }
7961e72d8d2Sderaadt 
7971e72d8d2Sderaadt     if (type == 'T')
7981e72d8d2Sderaadt     {
7991e72d8d2Sderaadt 	repos = update_dir;
8001e72d8d2Sderaadt 	update_dir = "";
8011e72d8d2Sderaadt     }
8021e72d8d2Sderaadt     else if (update_dir && *update_dir)
8031e72d8d2Sderaadt 	slash = "/";
8041e72d8d2Sderaadt     else
8051e72d8d2Sderaadt 	update_dir = "";
8061e72d8d2Sderaadt 
807461cc63eStholo     workdir = xmalloc (strlen (tilde) + strlen (PrCurDir) + strlen (slash)
808461cc63eStholo 		       + strlen (update_dir) + 10);
8091e72d8d2Sderaadt     (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
8101e72d8d2Sderaadt 
8111e72d8d2Sderaadt     /*
8121e72d8d2Sderaadt      * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
8131e72d8d2Sderaadt      * "repos"	is the Repository, relative to $CVSROOT where the RCS file is.
8141e72d8d2Sderaadt      *
8151e72d8d2Sderaadt      * "$workdir/$name" is the working file name.
8161e72d8d2Sderaadt      * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
8171e72d8d2Sderaadt      *
8181e72d8d2Sderaadt      * First, note that the history format was intended to save space, not
8191e72d8d2Sderaadt      * to be human readable.
8201e72d8d2Sderaadt      *
8211e72d8d2Sderaadt      * The working file directory ("workdir") and the Repository ("repos")
8221e72d8d2Sderaadt      * usually end with the same one or more directory elements.  To avoid
8231e72d8d2Sderaadt      * duplication (and save space), the "workdir" field ends with
8241e72d8d2Sderaadt      * an integer offset into the "repos" field.  This offset indicates the
8251e72d8d2Sderaadt      * beginning of the "tail" of "repos", after which all characters are
8261e72d8d2Sderaadt      * duplicates.
8271e72d8d2Sderaadt      *
8281e72d8d2Sderaadt      * In other words, if the "workdir" field has a '*' (a very stupid thing
8291e72d8d2Sderaadt      * to put in a filename) in it, then every thing following the last '*'
8301e72d8d2Sderaadt      * is a hex offset into "repos" of the first character from "repos" to
8311e72d8d2Sderaadt      * append to "workdir" to finish the pathname.
8321e72d8d2Sderaadt      *
8331e72d8d2Sderaadt      * It might be easier to look at an example:
8341e72d8d2Sderaadt      *
8351e72d8d2Sderaadt      *  M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
8361e72d8d2Sderaadt      *
8371e72d8d2Sderaadt      * Indicates that the workdir is really "~/work/cvs/examples", saving
8381e72d8d2Sderaadt      * 10 characters, where "~/work*d" would save 6 characters and mean that
8391e72d8d2Sderaadt      * the workdir is really "~/work/examples".  It will mean more on
8401e72d8d2Sderaadt      * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
8411e72d8d2Sderaadt      *
8421e72d8d2Sderaadt      * "workdir" is always an absolute pathname (~/xxx is an absolute path)
8431e72d8d2Sderaadt      * "repos" is always a relative pathname.  So we can assume that we will
8441e72d8d2Sderaadt      * never run into the top of "workdir" -- there will always be a '/' or
8451e72d8d2Sderaadt      * a '~' at the head of "workdir" that is not matched by anything in
8461e72d8d2Sderaadt      * "repos".  On the other hand, we *can* run off the top of "repos".
8471e72d8d2Sderaadt      *
8481e72d8d2Sderaadt      * Only "compress" if we save characters.
8491e72d8d2Sderaadt      */
8501e72d8d2Sderaadt 
8511e72d8d2Sderaadt     if (!repos)
8521e72d8d2Sderaadt 	repos = "";
8531e72d8d2Sderaadt 
8541e72d8d2Sderaadt     cp = workdir + strlen (workdir) - 1;
8551e72d8d2Sderaadt     cp2 = repos + strlen (repos) - 1;
8561e72d8d2Sderaadt     for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
8571e72d8d2Sderaadt 	i++;
8581e72d8d2Sderaadt 
8591e72d8d2Sderaadt     if (i > 2)
8601e72d8d2Sderaadt     {
8611e72d8d2Sderaadt 	i = strlen (repos) - i;
8621e72d8d2Sderaadt 	(void) sprintf ((cp + 1), "*%x", i);
8631e72d8d2Sderaadt     }
8641e72d8d2Sderaadt 
8651e72d8d2Sderaadt     if (!revs)
8661e72d8d2Sderaadt 	revs = "";
8671e72d8d2Sderaadt     line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
8681e72d8d2Sderaadt 		    + strlen (revs) + strlen (name) + 100);
869*4b3d160cSjca     sprintf (line, "%c%08llx|%s|%s|%s|%s|%s\n",
870*4b3d160cSjca 	     type, (long long) time ((time_t *) NULL),
8711e72d8d2Sderaadt 	     username, workdir, repos, revs, name);
8721e72d8d2Sderaadt 
8731e72d8d2Sderaadt     /* Lessen some race conditions on non-Posix-compliant hosts.  */
8741e72d8d2Sderaadt     if (lseek (fd, (off_t) 0, SEEK_END) == -1)
8751e72d8d2Sderaadt 	error (1, errno, "cannot seek to end of history file: %s", fname);
8761e72d8d2Sderaadt 
8771e72d8d2Sderaadt     if (write (fd, line, strlen (line)) < 0)
8781e72d8d2Sderaadt 	error (1, errno, "cannot write to history file: %s", fname);
8791e72d8d2Sderaadt     free (line);
8801e72d8d2Sderaadt     if (close (fd) != 0)
8811e72d8d2Sderaadt 	error (1, errno, "cannot close history file: %s", fname);
882461cc63eStholo     free (workdir);
883461cc63eStholo  out:
884461cc63eStholo     free (fname);
8851e72d8d2Sderaadt }
8861e72d8d2Sderaadt 
8871e72d8d2Sderaadt /*
8881e72d8d2Sderaadt  * save_user() adds a user name to the user list to select.  Zero-length
8891e72d8d2Sderaadt  *		username ("") matches any user.
8901e72d8d2Sderaadt  */
8911e72d8d2Sderaadt static void
save_user(name)8921e72d8d2Sderaadt save_user (name)
8931e72d8d2Sderaadt     char *name;
8941e72d8d2Sderaadt {
8951e72d8d2Sderaadt     if (user_count == user_max)
8961e72d8d2Sderaadt     {
8972ddaa231Sotto 	user_max = xsum (user_max, USER_INCREMENT);
8982ddaa231Sotto 	if (size_overflow_p (xtimes (user_max, sizeof (char *))))
8992ddaa231Sotto 	{
9002ddaa231Sotto 	    error (0, 0, "save_user: too many users");
9012ddaa231Sotto 	    return;
9022ddaa231Sotto 	}
9032ddaa231Sotto 	user_list = xrealloc (user_list, xtimes (user_max, sizeof (char *)));
9041e72d8d2Sderaadt     }
9051e72d8d2Sderaadt     user_list[user_count++] = xstrdup (name);
9061e72d8d2Sderaadt }
9071e72d8d2Sderaadt 
9081e72d8d2Sderaadt /*
9091e72d8d2Sderaadt  * save_file() adds file name and associated module to the file list to select.
9101e72d8d2Sderaadt  *
9111e72d8d2Sderaadt  * If "dir" is null, store a file name as is.
9121e72d8d2Sderaadt  * If "name" is null, store a directory name with a '*' on the front.
9131e72d8d2Sderaadt  * Else, store concatenated "dir/name".
9141e72d8d2Sderaadt  *
9151e72d8d2Sderaadt  * Later, in the "select" stage:
9161e72d8d2Sderaadt  *	- if it starts with '*', it is prefix-matched against the repository.
9171e72d8d2Sderaadt  *	- if it has a '/' in it, it is matched against the repository/file.
9181e72d8d2Sderaadt  *	- else it is matched against the file name.
9191e72d8d2Sderaadt  */
9201e72d8d2Sderaadt static void
save_file(dir,name,module)9211e72d8d2Sderaadt save_file (dir, name, module)
9221e72d8d2Sderaadt     char *dir;
9231e72d8d2Sderaadt     char *name;
9241e72d8d2Sderaadt     char *module;
9251e72d8d2Sderaadt {
9261e72d8d2Sderaadt     char *cp;
9271e72d8d2Sderaadt     struct file_list_str *fl;
9281e72d8d2Sderaadt 
9291e72d8d2Sderaadt     if (file_count == file_max)
9301e72d8d2Sderaadt     {
9312ddaa231Sotto 	file_max = xsum (file_max, FILE_INCREMENT);
9322ddaa231Sotto 	if (size_overflow_p (xtimes (file_max, sizeof (*fl))))
9332ddaa231Sotto 	{
9342ddaa231Sotto 	    error (0, 0, "save_file: too many files");
9352ddaa231Sotto 	    return;
9362ddaa231Sotto 	}
9372ddaa231Sotto 	file_list = xrealloc (file_list, xtimes (file_max, sizeof (*fl)));
9381e72d8d2Sderaadt     }
9391e72d8d2Sderaadt     fl = &file_list[file_count++];
9401e72d8d2Sderaadt     fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
9411e72d8d2Sderaadt     fl->l_module = module;
9421e72d8d2Sderaadt 
9431e72d8d2Sderaadt     if (dir && *dir)
9441e72d8d2Sderaadt     {
9451e72d8d2Sderaadt 	if (name && *name)
9461e72d8d2Sderaadt 	{
9471e72d8d2Sderaadt 	    (void) strcpy (cp, dir);
9481e72d8d2Sderaadt 	    (void) strcat (cp, "/");
9491e72d8d2Sderaadt 	    (void) strcat (cp, name);
9501e72d8d2Sderaadt 	}
9511e72d8d2Sderaadt 	else
9521e72d8d2Sderaadt 	{
9531e72d8d2Sderaadt 	    *cp++ = '*';
9541e72d8d2Sderaadt 	    (void) strcpy (cp, dir);
9551e72d8d2Sderaadt 	}
9561e72d8d2Sderaadt     }
9571e72d8d2Sderaadt     else
9581e72d8d2Sderaadt     {
9591e72d8d2Sderaadt 	if (name && *name)
9601e72d8d2Sderaadt 	{
9611e72d8d2Sderaadt 	    (void) strcpy (cp, name);
9621e72d8d2Sderaadt 	}
9631e72d8d2Sderaadt 	else
9641e72d8d2Sderaadt 	{
9651e72d8d2Sderaadt 	    error (0, 0, "save_file: null dir and file name");
9661e72d8d2Sderaadt 	}
9671e72d8d2Sderaadt     }
9681e72d8d2Sderaadt }
9691e72d8d2Sderaadt 
9701e72d8d2Sderaadt static void
save_module(module)9711e72d8d2Sderaadt save_module (module)
9721e72d8d2Sderaadt     char *module;
9731e72d8d2Sderaadt {
9741e72d8d2Sderaadt     if (mod_count == mod_max)
9751e72d8d2Sderaadt     {
9762ddaa231Sotto 	mod_max = xsum (mod_max, MODULE_INCREMENT);
9772ddaa231Sotto 	if (size_overflow_p (xtimes (mod_max, sizeof (char *))))
9782ddaa231Sotto 	{
9792ddaa231Sotto 	    error (0, 0, "save_module: too many modules");
9802ddaa231Sotto 	    return;
9812ddaa231Sotto 	}
9822ddaa231Sotto 	mod_list = xrealloc (mod_list, xtimes (mod_max, sizeof (char *)));
9831e72d8d2Sderaadt     }
9841e72d8d2Sderaadt     mod_list[mod_count++] = xstrdup (module);
9851e72d8d2Sderaadt }
9861e72d8d2Sderaadt 
9871e72d8d2Sderaadt static void
expand_modules()9881e72d8d2Sderaadt expand_modules ()
9891e72d8d2Sderaadt {
9901e72d8d2Sderaadt }
9911e72d8d2Sderaadt 
9921e72d8d2Sderaadt /* fill_hrec
9931e72d8d2Sderaadt  *
9941e72d8d2Sderaadt  * Take a ptr to 7-part history line, ending with a newline, for example:
9951e72d8d2Sderaadt  *
9961e72d8d2Sderaadt  *	M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
9971e72d8d2Sderaadt  *
9981e72d8d2Sderaadt  * Split it into 7 parts and drop the parts into a "struct hrec".
9991e72d8d2Sderaadt  * Return a pointer to the character following the newline.
1000e77048c1Stholo  *
10011e72d8d2Sderaadt  */
10021e72d8d2Sderaadt 
100343c1707eStholo #define NEXT_BAR(here) do { \
100443c1707eStholo 	while (isspace(*line)) line++; \
100543c1707eStholo 	hr->here = line; \
100643c1707eStholo 	while ((c = *line++) && c != '|') ; \
100743c1707eStholo 	if (!c) return; line[-1] = '\0'; \
100843c1707eStholo 	} while (0)
10091e72d8d2Sderaadt 
1010e77048c1Stholo static void
fill_hrec(line,hr)10111e72d8d2Sderaadt fill_hrec (line, hr)
10121e72d8d2Sderaadt     char *line;
10131e72d8d2Sderaadt     struct hrec *hr;
10141e72d8d2Sderaadt {
1015e77048c1Stholo     char *cp;
10161e72d8d2Sderaadt     int c;
10171e72d8d2Sderaadt 
101843c1707eStholo     hr->type = hr->user = hr->dir = hr->repos = hr->rev = hr->file =
101943c1707eStholo 	hr->end = hr->mod = NULL;
102043c1707eStholo     hr->date = -1;
102143c1707eStholo     hr->idx = ++hrec_idx;
1022e77048c1Stholo 
1023c71bc7e2Stholo     while (isspace ((unsigned char) *line))
10241e72d8d2Sderaadt 	line++;
10251e72d8d2Sderaadt 
10261e72d8d2Sderaadt     hr->type = line++;
102743c1707eStholo     hr->date = strtoul (line, &cp, 16);
102843c1707eStholo     if (cp == line || *cp != '|')
1029e77048c1Stholo 	return;
103043c1707eStholo     line = cp + 1;
10311e72d8d2Sderaadt     NEXT_BAR (user);
10321e72d8d2Sderaadt     NEXT_BAR (dir);
10331e72d8d2Sderaadt     if ((cp = strrchr (hr->dir, '*')) != NULL)
10341e72d8d2Sderaadt     {
10351e72d8d2Sderaadt 	*cp++ = '\0';
103643c1707eStholo 	hr->end = line + strtoul (cp, NULL, 16);
10371e72d8d2Sderaadt     }
10381e72d8d2Sderaadt     else
10391e72d8d2Sderaadt 	hr->end = line - 1;		/* A handy pointer to '\0' */
10401e72d8d2Sderaadt     NEXT_BAR (repos);
10411e72d8d2Sderaadt     NEXT_BAR (rev);
1042461cc63eStholo     if (strchr ("FOET", *(hr->type)))
10431e72d8d2Sderaadt 	hr->mod = line;
10441e72d8d2Sderaadt 
1045e77048c1Stholo     NEXT_BAR (file);
10461e72d8d2Sderaadt }
10471e72d8d2Sderaadt 
1048e77048c1Stholo 
1049e77048c1Stholo #ifndef STAT_BLOCKSIZE
1050e77048c1Stholo #if HAVE_ST_BLKSIZE
1051e77048c1Stholo #define STAT_BLOCKSIZE(s) (s).st_blksize
1052e77048c1Stholo #else
1053e77048c1Stholo #define STAT_BLOCKSIZE(s) (4 * 1024)
1054e77048c1Stholo #endif
1055e77048c1Stholo #endif
1056e77048c1Stholo 
1057e77048c1Stholo 
10581e72d8d2Sderaadt /* read_hrecs's job is to read the history file and fill in all the "hrec"
10591e72d8d2Sderaadt  * (history record) array elements with the ones we need to print.
10601e72d8d2Sderaadt  *
10611e72d8d2Sderaadt  * Logic:
1062e77048c1Stholo  * - Read a block from the file.
1063e77048c1Stholo  * - Walk through the block parsing line into hr records.
1064e77048c1Stholo  * - if the hr isn't used, free its strings, if it is, bump the hrec counter
1065e77048c1Stholo  * - at the end of a block, copy the end of the current block to the start
1066e77048c1Stholo  * of space for the next block, then read in the next block.  If we get less
1067e77048c1Stholo  * than the whole block, we're done.
10681e72d8d2Sderaadt  */
10691e72d8d2Sderaadt static void
read_hrecs(fname)10701e72d8d2Sderaadt read_hrecs (fname)
10711e72d8d2Sderaadt     char *fname;
10721e72d8d2Sderaadt {
107343c1707eStholo     unsigned char *cpstart, *cpend, *cp, *nl;
1074e77048c1Stholo     char *hrline;
1075e77048c1Stholo     int i;
1076e77048c1Stholo     int fd;
10771e72d8d2Sderaadt     struct stat st_buf;
10781e72d8d2Sderaadt 
107950bf276cStholo     if ((fd = CVS_OPEN (fname, O_RDONLY | OPEN_BINARY)) < 0)
10801e72d8d2Sderaadt 	error (1, errno, "cannot open history file: %s", fname);
10811e72d8d2Sderaadt 
10821e72d8d2Sderaadt     if (fstat (fd, &st_buf) < 0)
10831e72d8d2Sderaadt 	error (1, errno, "can't stat history file");
10841e72d8d2Sderaadt 
1085e77048c1Stholo     if (!(st_buf.st_size))
10861e72d8d2Sderaadt 	error (1, 0, "history file is empty");
10871e72d8d2Sderaadt 
1088e77048c1Stholo     cpstart = xmalloc (2 * STAT_BLOCKSIZE(st_buf));
1089e77048c1Stholo     cpstart[0] = '\0';
109043c1707eStholo     cp = cpend = cpstart;
10911e72d8d2Sderaadt 
10921e72d8d2Sderaadt     hrec_max = HREC_INCREMENT;
1093e77048c1Stholo     hrec_head = xmalloc (hrec_max * sizeof (struct hrec));
109443c1707eStholo     hrec_idx = 0;
10951e72d8d2Sderaadt 
1096e77048c1Stholo     for (;;)
10971e72d8d2Sderaadt     {
109843c1707eStholo 	for (nl = cp; nl < cpend && *nl != '\n'; nl++)
1099e77048c1Stholo 	    if (!isprint(*nl)) *nl = ' ';
1100e77048c1Stholo 
110143c1707eStholo 	if (nl >= cpend)
1102e77048c1Stholo 	{
1103e77048c1Stholo 	    if (nl - cp >= STAT_BLOCKSIZE(st_buf))
1104e77048c1Stholo 	    {
110543c1707eStholo 		error(1, 0, "history line %ld too long (> %lu)", hrec_idx + 1,
1106e77048c1Stholo 		      (unsigned long) STAT_BLOCKSIZE(st_buf));
1107e77048c1Stholo 	    }
1108e77048c1Stholo 	    if (nl > cp)
1109e77048c1Stholo 		memmove (cpstart, cp, nl - cp);
1110e77048c1Stholo 	    nl = cpstart + (nl - cp);
1111e77048c1Stholo 	    cp = cpstart;
1112e77048c1Stholo 	    i = read (fd, nl, STAT_BLOCKSIZE(st_buf));
1113e77048c1Stholo 	    if (i > 0)
1114e77048c1Stholo 	    {
111543c1707eStholo 		cpend = nl + i;
111643c1707eStholo 		*cpend = '\0';
1117e77048c1Stholo 		continue;
1118e77048c1Stholo 	    }
1119e77048c1Stholo 	    if (i < 0)
1120e77048c1Stholo 		error (1, errno, "error reading history file");
1121e77048c1Stholo 	    if (nl == cp) break;
112243c1707eStholo 	    error (0, 0, "warning: no newline at end of history file");
1123e77048c1Stholo 	}
1124e77048c1Stholo 	*nl = '\0';
1125e77048c1Stholo 
11261e72d8d2Sderaadt 	if (hrec_count == hrec_max)
11271e72d8d2Sderaadt 	{
11281e72d8d2Sderaadt 	    struct hrec *old_head = hrec_head;
11291e72d8d2Sderaadt 
11301e72d8d2Sderaadt 	    hrec_max += HREC_INCREMENT;
1131e77048c1Stholo 	    hrec_head = xrealloc ((char *) hrec_head,
11321e72d8d2Sderaadt 				  hrec_max * sizeof (struct hrec));
11331e72d8d2Sderaadt 	    if (last_since_tag)
11341e72d8d2Sderaadt 		last_since_tag = hrec_head + (last_since_tag - old_head);
11351e72d8d2Sderaadt 	    if (last_backto)
11361e72d8d2Sderaadt 		last_backto = hrec_head + (last_backto - old_head);
11371e72d8d2Sderaadt 	}
11381e72d8d2Sderaadt 
1139e77048c1Stholo 	/* fill_hrec dates from when history read the entire
1140e77048c1Stholo 	   history file in one chunk, and then records were pulled out
1141e77048c1Stholo 	   by pointing to the various parts of this big chunk.  This is
1142e77048c1Stholo 	   why there are ugly hacks here:  I don't want to completely
1143e77048c1Stholo 	   re-write the whole history stuff right now.  */
11441e72d8d2Sderaadt 
1145e77048c1Stholo 	hrline = xstrdup ((char *)cp);
1146e77048c1Stholo 	fill_hrec (hrline, &hrec_head[hrec_count]);
1147e77048c1Stholo 	if (select_hrec (&hrec_head[hrec_count]))
11481e72d8d2Sderaadt 	    hrec_count++;
1149e77048c1Stholo 	else
1150e77048c1Stholo 	    free(hrline);
1151e77048c1Stholo 
1152e77048c1Stholo 	cp = nl + 1;
11531e72d8d2Sderaadt     }
1154e77048c1Stholo     free (cpstart);
1155e77048c1Stholo     close (fd);
11561e72d8d2Sderaadt 
11571e72d8d2Sderaadt     /* Special selection problem: If "since_tag" is set, we have saved every
11581e72d8d2Sderaadt      * record from the 1st occurrence of "since_tag", when we want to save
11591e72d8d2Sderaadt      * records since the *last* occurrence of "since_tag".  So what we have
11601e72d8d2Sderaadt      * to do is bump hrec_head forward and reduce hrec_count accordingly.
11611e72d8d2Sderaadt      */
11621e72d8d2Sderaadt     if (last_since_tag)
11631e72d8d2Sderaadt     {
11641e72d8d2Sderaadt 	hrec_count -= (last_since_tag - hrec_head);
11651e72d8d2Sderaadt 	hrec_head = last_since_tag;
11661e72d8d2Sderaadt     }
11671e72d8d2Sderaadt 
11681e72d8d2Sderaadt     /* Much the same thing is necessary for the "backto" option. */
11691e72d8d2Sderaadt     if (last_backto)
11701e72d8d2Sderaadt     {
11711e72d8d2Sderaadt 	hrec_count -= (last_backto - hrec_head);
11721e72d8d2Sderaadt 	hrec_head = last_backto;
11731e72d8d2Sderaadt     }
11741e72d8d2Sderaadt }
11751e72d8d2Sderaadt 
11761e72d8d2Sderaadt /* Utility program for determining whether "find" is inside "string" */
11771e72d8d2Sderaadt static int
within(find,string)11781e72d8d2Sderaadt within (find, string)
11791e72d8d2Sderaadt     char *find, *string;
11801e72d8d2Sderaadt {
11811e72d8d2Sderaadt     int c, len;
11821e72d8d2Sderaadt 
11831e72d8d2Sderaadt     if (!find || !string)
11841e72d8d2Sderaadt 	return (0);
11851e72d8d2Sderaadt 
11861e72d8d2Sderaadt     c = *find++;
11871e72d8d2Sderaadt     len = strlen (find);
11881e72d8d2Sderaadt 
11891e72d8d2Sderaadt     while (*string)
11901e72d8d2Sderaadt     {
11911e72d8d2Sderaadt 	if (!(string = strchr (string, c)))
11921e72d8d2Sderaadt 	    return (0);
11931e72d8d2Sderaadt 	string++;
11941e72d8d2Sderaadt 	if (!strncmp (find, string, len))
11951e72d8d2Sderaadt 	    return (1);
11961e72d8d2Sderaadt     }
11971e72d8d2Sderaadt     return (0);
11981e72d8d2Sderaadt }
11991e72d8d2Sderaadt 
12001e72d8d2Sderaadt /* The purpose of "select_hrec" is to apply the selection criteria based on
12011e72d8d2Sderaadt  * the command arguments and defaults and return a flag indicating whether
12021e72d8d2Sderaadt  * this record should be remembered for printing.
12031e72d8d2Sderaadt  */
12041e72d8d2Sderaadt static int
select_hrec(hr)12051e72d8d2Sderaadt select_hrec (hr)
12061e72d8d2Sderaadt     struct hrec *hr;
12071e72d8d2Sderaadt {
12081e72d8d2Sderaadt     char **cpp, *cp, *cp2;
12091e72d8d2Sderaadt     struct file_list_str *fl;
12101e72d8d2Sderaadt     int count;
12111e72d8d2Sderaadt 
121243c1707eStholo     /* basic validity checking */
121343c1707eStholo     if (!hr->type || !hr->user || !hr->dir || !hr->repos || !hr->rev ||
121443c1707eStholo 	!hr->file || !hr->end)
121543c1707eStholo     {
121643c1707eStholo 	error (0, 0, "warning: history line %ld invalid", hr->idx);
121743c1707eStholo 	return (0);
121843c1707eStholo     }
121943c1707eStholo 
12201e72d8d2Sderaadt     /* "Since" checking:  The argument parser guarantees that only one of the
12211e72d8d2Sderaadt      *			  following four choices is set:
12221e72d8d2Sderaadt      *
1223b6f6614eStholo      * 1. If "since_date" is set, it contains the date specified on the
12241e72d8d2Sderaadt      *    command line. hr->date fields earlier than "since_date" are ignored.
12251e72d8d2Sderaadt      * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
12261e72d8d2Sderaadt      *    number (which is of limited use) or a symbolic TAG.  Each RCS file
12271e72d8d2Sderaadt      *    is examined and the date on the specified revision (or the revision
12281e72d8d2Sderaadt      *    corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
12291e72d8d2Sderaadt      *    compared against hr->date as in 1. above.
12301e72d8d2Sderaadt      * 3. If "since_tag" is set, matching tag records are saved.  The field
12311e72d8d2Sderaadt      *    "last_since_tag" is set to the last one of these.  Since we don't
12321e72d8d2Sderaadt      *    know where the last one will be, all records are saved from the
12331e72d8d2Sderaadt      *    first occurrence of the TAG.  Later, at the end of "select_hrec"
12341e72d8d2Sderaadt      *    records before the last occurrence of "since_tag" are skipped.
12351e72d8d2Sderaadt      * 4. If "backto" is set, all records with a module name or file name
12361e72d8d2Sderaadt      *    matching "backto" are saved.  In addition, all records with a
12371e72d8d2Sderaadt      *    repository field with a *prefix* matching "backto" are saved.
12381e72d8d2Sderaadt      *    The field "last_backto" is set to the last one of these.  As in
12391e72d8d2Sderaadt      *    3. above, "select_hrec" adjusts to include the last one later on.
12401e72d8d2Sderaadt      */
12411e72d8d2Sderaadt     if (since_date)
12421e72d8d2Sderaadt     {
1243b6f6614eStholo 	char *ourdate = date_from_time_t (hr->date);
1244e77048c1Stholo 	count = RCS_datecmp (ourdate, since_date);
1245b6f6614eStholo 	free (ourdate);
1246e77048c1Stholo 	if (count < 0)
1247e77048c1Stholo 	    return (0);
12481e72d8d2Sderaadt     }
12491e72d8d2Sderaadt     else if (*since_rev)
12501e72d8d2Sderaadt     {
12511e72d8d2Sderaadt 	Vers_TS *vers;
12521e72d8d2Sderaadt 	time_t t;
125350bf276cStholo 	struct file_info finfo;
12541e72d8d2Sderaadt 
125550bf276cStholo 	memset (&finfo, 0, sizeof finfo);
125650bf276cStholo 	finfo.file = hr->file;
125750bf276cStholo 	/* Not used, so don't worry about it.  */
125850bf276cStholo 	finfo.update_dir = NULL;
125950bf276cStholo 	finfo.fullname = finfo.file;
126050bf276cStholo 	finfo.repository = hr->repos;
126150bf276cStholo 	finfo.entries = NULL;
126250bf276cStholo 	finfo.rcs = NULL;
126350bf276cStholo 
126450bf276cStholo 	vers = Version_TS (&finfo, (char *) NULL, since_rev, (char *) NULL,
126550bf276cStholo 			   1, 0);
12661e72d8d2Sderaadt 	if (vers->vn_rcs)
12671e72d8d2Sderaadt 	{
12681e72d8d2Sderaadt 	    if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
12691e72d8d2Sderaadt 		!= (time_t) 0)
12701e72d8d2Sderaadt 	    {
12711e72d8d2Sderaadt 		if (hr->date < t)
12721e72d8d2Sderaadt 		{
12731e72d8d2Sderaadt 		    freevers_ts (&vers);
12741e72d8d2Sderaadt 		    return (0);
12751e72d8d2Sderaadt 		}
12761e72d8d2Sderaadt 	    }
12771e72d8d2Sderaadt 	}
12781e72d8d2Sderaadt 	freevers_ts (&vers);
12791e72d8d2Sderaadt     }
12801e72d8d2Sderaadt     else if (*since_tag)
12811e72d8d2Sderaadt     {
12821e72d8d2Sderaadt 	if (*(hr->type) == 'T')
12831e72d8d2Sderaadt 	{
12841e72d8d2Sderaadt 	    /*
12851e72d8d2Sderaadt 	     * A 'T'ag record, the "rev" field holds the tag to be set,
12861e72d8d2Sderaadt 	     * while the "repos" field holds "D"elete, "A"dd or a rev.
12871e72d8d2Sderaadt 	     */
12881e72d8d2Sderaadt 	    if (within (since_tag, hr->rev))
12891e72d8d2Sderaadt 	    {
12901e72d8d2Sderaadt 		last_since_tag = hr;
12911e72d8d2Sderaadt 		return (1);
12921e72d8d2Sderaadt 	    }
12931e72d8d2Sderaadt 	    else
12941e72d8d2Sderaadt 		return (0);
12951e72d8d2Sderaadt 	}
12961e72d8d2Sderaadt 	if (!last_since_tag)
12971e72d8d2Sderaadt 	    return (0);
12981e72d8d2Sderaadt     }
12991e72d8d2Sderaadt     else if (*backto)
13001e72d8d2Sderaadt     {
13011e72d8d2Sderaadt 	if (within (backto, hr->file) || within (backto, hr->mod) ||
13021e72d8d2Sderaadt 	    within (backto, hr->repos))
13031e72d8d2Sderaadt 	    last_backto = hr;
13041e72d8d2Sderaadt 	else
13051e72d8d2Sderaadt 	    return (0);
13061e72d8d2Sderaadt     }
13071e72d8d2Sderaadt 
13081e72d8d2Sderaadt     /* User checking:
13091e72d8d2Sderaadt      *
13101e72d8d2Sderaadt      * Run down "user_list", match username ("" matches anything)
13111e72d8d2Sderaadt      * If "" is not there and actual username is not there, return failure.
13121e72d8d2Sderaadt      */
13131e72d8d2Sderaadt     if (user_list && hr->user)
13141e72d8d2Sderaadt     {
13151e72d8d2Sderaadt 	for (cpp = user_list, count = user_count; count; cpp++, count--)
13161e72d8d2Sderaadt 	{
13171e72d8d2Sderaadt 	    if (!**cpp)
13181e72d8d2Sderaadt 		break;			/* null user == accept */
13191e72d8d2Sderaadt 	    if (!strcmp (hr->user, *cpp))	/* found listed user */
13201e72d8d2Sderaadt 		break;
13211e72d8d2Sderaadt 	}
13221e72d8d2Sderaadt 	if (!count)
13231e72d8d2Sderaadt 	    return (0);			/* Not this user */
13241e72d8d2Sderaadt     }
13251e72d8d2Sderaadt 
13261e72d8d2Sderaadt     /* Record type checking:
13271e72d8d2Sderaadt      *
13281e72d8d2Sderaadt      * 1. If Record type is not in rec_types field, skip it.
13291e72d8d2Sderaadt      * 2. If mod_list is null, keep everything.  Otherwise keep only modules
13301e72d8d2Sderaadt      *    on mod_list.
13311e72d8d2Sderaadt      * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list".  If
13321e72d8d2Sderaadt      *    file_list is null, keep everything.  Otherwise, keep only files on
13331e72d8d2Sderaadt      *    file_list, matched appropriately.
13341e72d8d2Sderaadt      */
13351e72d8d2Sderaadt     if (!strchr (rec_types, *(hr->type)))
13361e72d8d2Sderaadt 	return (0);
1337461cc63eStholo     if (!strchr ("TFOE", *(hr->type)))	/* Don't bother with "file" if "TFOE" */
13381e72d8d2Sderaadt     {
13391e72d8d2Sderaadt 	if (file_list)			/* If file_list is null, accept all */
13401e72d8d2Sderaadt 	{
13411e72d8d2Sderaadt 	    for (fl = file_list, count = file_count; count; fl++, count--)
13421e72d8d2Sderaadt 	    {
13431e72d8d2Sderaadt 		/* 1. If file_list entry starts with '*', skip the '*' and
13441e72d8d2Sderaadt 		 *    compare it against the repository in the hrec.
13451e72d8d2Sderaadt 		 * 2. If file_list entry has a '/' in it, compare it against
13461e72d8d2Sderaadt 		 *    the concatenation of the repository and file from hrec.
13471e72d8d2Sderaadt 		 * 3. Else compare the file_list entry against the hrec file.
13481e72d8d2Sderaadt 		 */
1349461cc63eStholo 		char *cmpfile = NULL;
13501e72d8d2Sderaadt 
13511e72d8d2Sderaadt 		if (*(cp = fl->l_file) == '*')
13521e72d8d2Sderaadt 		{
13531e72d8d2Sderaadt 		    cp++;
13541e72d8d2Sderaadt 		    /* if argument to -p is a prefix of repository */
13551e72d8d2Sderaadt 		    if (!strncmp (cp, hr->repos, strlen (cp)))
13561e72d8d2Sderaadt 		    {
13571e72d8d2Sderaadt 			hr->mod = fl->l_module;
13581e72d8d2Sderaadt 			break;
13591e72d8d2Sderaadt 		    }
13601e72d8d2Sderaadt 		}
13611e72d8d2Sderaadt 		else
13621e72d8d2Sderaadt 		{
13631e72d8d2Sderaadt 		    if (strchr (cp, '/'))
13641e72d8d2Sderaadt 		    {
1365461cc63eStholo 			cmpfile = xmalloc (strlen (hr->repos)
1366461cc63eStholo 					   + strlen (hr->file)
1367461cc63eStholo 					   + 10);
1368461cc63eStholo 			(void) sprintf (cmpfile, "%s/%s",
13691e72d8d2Sderaadt 					hr->repos, hr->file);
1370461cc63eStholo 			cp2 = cmpfile;
13711e72d8d2Sderaadt 		    }
13721e72d8d2Sderaadt 		    else
13731e72d8d2Sderaadt 		    {
13741e72d8d2Sderaadt 			cp2 = hr->file;
13751e72d8d2Sderaadt 		    }
13761e72d8d2Sderaadt 
13771e72d8d2Sderaadt 		    /* if requested file is found within {repos}/file fields */
13781e72d8d2Sderaadt 		    if (within (cp, cp2))
13791e72d8d2Sderaadt 		    {
13801e72d8d2Sderaadt 			hr->mod = fl->l_module;
13811e72d8d2Sderaadt 			break;
13821e72d8d2Sderaadt 		    }
1383461cc63eStholo 		    if (cmpfile != NULL)
1384461cc63eStholo 			free (cmpfile);
13851e72d8d2Sderaadt 		}
13861e72d8d2Sderaadt 	    }
13871e72d8d2Sderaadt 	    if (!count)
13881e72d8d2Sderaadt 		return (0);		/* String specified and no match */
13891e72d8d2Sderaadt 	}
13901e72d8d2Sderaadt     }
13911e72d8d2Sderaadt     if (mod_list)
13921e72d8d2Sderaadt     {
13931e72d8d2Sderaadt 	for (cpp = mod_list, count = mod_count; count; cpp++, count--)
13941e72d8d2Sderaadt 	{
13951e72d8d2Sderaadt 	    if (hr->mod && !strcmp (hr->mod, *cpp))	/* found module */
13961e72d8d2Sderaadt 		break;
13971e72d8d2Sderaadt 	}
13981e72d8d2Sderaadt 	if (!count)
13991e72d8d2Sderaadt 	    return (0);	/* Module specified & this record is not one of them. */
14001e72d8d2Sderaadt     }
14011e72d8d2Sderaadt 
14021e72d8d2Sderaadt     return (1);		/* Select this record unless rejected above. */
14031e72d8d2Sderaadt }
14041e72d8d2Sderaadt 
14051e72d8d2Sderaadt /* The "sort_order" routine (when handed to qsort) has arranged for the
14061e72d8d2Sderaadt  * hrecs files to be in the right order for the report.
14071e72d8d2Sderaadt  *
14081e72d8d2Sderaadt  * Most of the "selections" are done in the select_hrec routine, but some
14091e72d8d2Sderaadt  * selections are more easily done after the qsort by "accept_hrec".
14101e72d8d2Sderaadt  */
14111e72d8d2Sderaadt static void
report_hrecs()14121e72d8d2Sderaadt report_hrecs ()
14131e72d8d2Sderaadt {
14141e72d8d2Sderaadt     struct hrec *hr, *lr;
14151e72d8d2Sderaadt     struct tm *tm;
14161e72d8d2Sderaadt     int i, count, ty;
14171e72d8d2Sderaadt     char *cp;
14181e72d8d2Sderaadt     int user_len, file_len, rev_len, mod_len, repos_len;
14191e72d8d2Sderaadt 
14201e72d8d2Sderaadt     if (*since_tag && !last_since_tag)
14211e72d8d2Sderaadt     {
14221e72d8d2Sderaadt 	(void) printf ("No tag found: %s\n", since_tag);
14231e72d8d2Sderaadt 	return;
14241e72d8d2Sderaadt     }
14251e72d8d2Sderaadt     else if (*backto && !last_backto)
14261e72d8d2Sderaadt     {
14271e72d8d2Sderaadt 	(void) printf ("No module, file or repository with: %s\n", backto);
14281e72d8d2Sderaadt 	return;
14291e72d8d2Sderaadt     }
14301e72d8d2Sderaadt     else if (hrec_count < 1)
14311e72d8d2Sderaadt     {
14321e72d8d2Sderaadt 	(void) printf ("No records selected.\n");
14331e72d8d2Sderaadt 	return;
14341e72d8d2Sderaadt     }
14351e72d8d2Sderaadt 
14361e72d8d2Sderaadt     user_len = file_len = rev_len = mod_len = repos_len = 0;
14371e72d8d2Sderaadt 
14381e72d8d2Sderaadt     /* Run through lists and find maximum field widths */
14391e72d8d2Sderaadt     hr = lr = hrec_head;
14401e72d8d2Sderaadt     hr++;
14411e72d8d2Sderaadt     for (count = hrec_count; count--; lr = hr, hr++)
14421e72d8d2Sderaadt     {
1443461cc63eStholo 	char *repos;
14441e72d8d2Sderaadt 
14451e72d8d2Sderaadt 	if (!count)
14461e72d8d2Sderaadt 	    hr = NULL;
14471e72d8d2Sderaadt 	if (!accept_hrec (lr, hr))
14481e72d8d2Sderaadt 	    continue;
14491e72d8d2Sderaadt 
14501e72d8d2Sderaadt 	ty = *(lr->type);
1451461cc63eStholo 	repos = xstrdup (lr->repos);
14521e72d8d2Sderaadt 	if ((cp = strrchr (repos, '/')) != NULL)
14531e72d8d2Sderaadt 	{
14541e72d8d2Sderaadt 	    if (lr->mod && !strcmp (++cp, lr->mod))
14551e72d8d2Sderaadt 	    {
14561e72d8d2Sderaadt 		(void) strcpy (cp, "*");
14571e72d8d2Sderaadt 	    }
14581e72d8d2Sderaadt 	}
14591e72d8d2Sderaadt 	if ((i = strlen (lr->user)) > user_len)
14601e72d8d2Sderaadt 	    user_len = i;
14611e72d8d2Sderaadt 	if ((i = strlen (lr->file)) > file_len)
14621e72d8d2Sderaadt 	    file_len = i;
14631e72d8d2Sderaadt 	if (ty != 'T' && (i = strlen (repos)) > repos_len)
14641e72d8d2Sderaadt 	    repos_len = i;
14651e72d8d2Sderaadt 	if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
14661e72d8d2Sderaadt 	    rev_len = i;
14671e72d8d2Sderaadt 	if (lr->mod && (i = strlen (lr->mod)) > mod_len)
14681e72d8d2Sderaadt 	    mod_len = i;
1469461cc63eStholo 	free (repos);
14701e72d8d2Sderaadt     }
14711e72d8d2Sderaadt 
14721e72d8d2Sderaadt     /* Walk through hrec array setting "lr" (Last Record) to each element.
14731e72d8d2Sderaadt      * "hr" points to the record following "lr" -- It is NULL in the last
14741e72d8d2Sderaadt      * pass.
14751e72d8d2Sderaadt      *
14761e72d8d2Sderaadt      * There are two sections in the loop below:
14771e72d8d2Sderaadt      * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
14781e72d8d2Sderaadt      *    decide whether the record should be printed.
14791e72d8d2Sderaadt      * 2. Based on the record type, format and print the data.
14801e72d8d2Sderaadt      */
14811e72d8d2Sderaadt     for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
14821e72d8d2Sderaadt     {
1483461cc63eStholo 	char *workdir;
1484461cc63eStholo 	char *repos;
14851e72d8d2Sderaadt 
14861e72d8d2Sderaadt 	if (!hrec_count)
14871e72d8d2Sderaadt 	    hr = NULL;
14881e72d8d2Sderaadt 	if (!accept_hrec (lr, hr))
14891e72d8d2Sderaadt 	    continue;
14901e72d8d2Sderaadt 
14911e72d8d2Sderaadt 	ty = *(lr->type);
14921e72d8d2Sderaadt 	if (!tz_local)
14931e72d8d2Sderaadt 	{
14941e72d8d2Sderaadt 	    time_t t = lr->date + tz_seconds_east_of_GMT;
14951e72d8d2Sderaadt 	    tm = gmtime (&t);
14961e72d8d2Sderaadt 	}
14971e72d8d2Sderaadt 	else
14981e72d8d2Sderaadt 	    tm = localtime (&(lr->date));
14992286d8edStholo 
1500e77048c1Stholo 	(void) printf ("%c %04d-%02d-%02d %02d:%02d %s %-*s", ty,
1501e77048c1Stholo 		  tm->tm_year+1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
1502e77048c1Stholo 		  tm->tm_min, tz_name, user_len, lr->user);
15031e72d8d2Sderaadt 
1504461cc63eStholo 	workdir = xmalloc (strlen (lr->dir) + strlen (lr->end) + 10);
15051e72d8d2Sderaadt 	(void) sprintf (workdir, "%s%s", lr->dir, lr->end);
15061e72d8d2Sderaadt 	if ((cp = strrchr (workdir, '/')) != NULL)
15071e72d8d2Sderaadt 	{
15081e72d8d2Sderaadt 	    if (lr->mod && !strcmp (++cp, lr->mod))
15091e72d8d2Sderaadt 	    {
15101e72d8d2Sderaadt 		(void) strcpy (cp, "*");
15111e72d8d2Sderaadt 	    }
15121e72d8d2Sderaadt 	}
1513461cc63eStholo 	repos = xmalloc (strlen (lr->repos) + 10);
15141e72d8d2Sderaadt 	(void) strcpy (repos, lr->repos);
15151e72d8d2Sderaadt 	if ((cp = strrchr (repos, '/')) != NULL)
15161e72d8d2Sderaadt 	{
15171e72d8d2Sderaadt 	    if (lr->mod && !strcmp (++cp, lr->mod))
15181e72d8d2Sderaadt 	    {
15191e72d8d2Sderaadt 		(void) strcpy (cp, "*");
15201e72d8d2Sderaadt 	    }
15211e72d8d2Sderaadt 	}
15221e72d8d2Sderaadt 
15231e72d8d2Sderaadt 	switch (ty)
15241e72d8d2Sderaadt 	{
15251e72d8d2Sderaadt 	    case 'T':
15261e72d8d2Sderaadt 		/* 'T'ag records: repository is a "tag type", rev is the tag */
15271e72d8d2Sderaadt 		(void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
15281e72d8d2Sderaadt 			       repos);
15291e72d8d2Sderaadt 		if (working)
15301e72d8d2Sderaadt 		    (void) printf (" {%s}", workdir);
15311e72d8d2Sderaadt 		break;
15321e72d8d2Sderaadt 	    case 'F':
1533461cc63eStholo 	    case 'E':
15341e72d8d2Sderaadt 	    case 'O':
15351e72d8d2Sderaadt 		if (lr->rev && *(lr->rev))
15361e72d8d2Sderaadt 		    (void) printf (" [%s]", lr->rev);
15371e72d8d2Sderaadt 		(void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
153850bf276cStholo 			       mod_len + 1 - (int) strlen (lr->mod),
153950bf276cStholo 			       "=", workdir);
15401e72d8d2Sderaadt 		break;
15411e72d8d2Sderaadt 	    case 'W':
15421e72d8d2Sderaadt 	    case 'U':
15431e72d8d2Sderaadt 	    case 'C':
15441e72d8d2Sderaadt 	    case 'G':
15451e72d8d2Sderaadt 	    case 'M':
15461e72d8d2Sderaadt 	    case 'A':
15471e72d8d2Sderaadt 	    case 'R':
15481e72d8d2Sderaadt 		(void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
15491e72d8d2Sderaadt 			       file_len, lr->file, repos_len, repos,
15501e72d8d2Sderaadt 			       lr->mod ? lr->mod : "", workdir);
15511e72d8d2Sderaadt 		break;
15521e72d8d2Sderaadt 	    default:
15531e72d8d2Sderaadt 		(void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
15541e72d8d2Sderaadt 		break;
15551e72d8d2Sderaadt 	}
15561e72d8d2Sderaadt 	(void) putchar ('\n');
1557461cc63eStholo 	free (workdir);
1558461cc63eStholo 	free (repos);
15591e72d8d2Sderaadt     }
15601e72d8d2Sderaadt }
15611e72d8d2Sderaadt 
15621e72d8d2Sderaadt static int
accept_hrec(lr,hr)15631e72d8d2Sderaadt accept_hrec (lr, hr)
15641e72d8d2Sderaadt     struct hrec *hr, *lr;
15651e72d8d2Sderaadt {
15661e72d8d2Sderaadt     int ty;
15671e72d8d2Sderaadt 
15681e72d8d2Sderaadt     ty = *(lr->type);
15691e72d8d2Sderaadt 
15701e72d8d2Sderaadt     if (last_since_tag && ty == 'T')
15711e72d8d2Sderaadt 	return (1);
15721e72d8d2Sderaadt 
15731e72d8d2Sderaadt     if (v_checkout)
15741e72d8d2Sderaadt     {
15751e72d8d2Sderaadt 	if (ty != 'O')
15761e72d8d2Sderaadt 	    return (0);			/* Only interested in 'O' records */
15771e72d8d2Sderaadt 
15781e72d8d2Sderaadt 	/* We want to identify all the states that cause the next record
15791e72d8d2Sderaadt 	 * ("hr") to be different from the current one ("lr") and only
15801e72d8d2Sderaadt 	 * print a line at the allowed boundaries.
15811e72d8d2Sderaadt 	 */
15821e72d8d2Sderaadt 
15831e72d8d2Sderaadt 	if (!hr ||			/* The last record */
15841e72d8d2Sderaadt 	    strcmp (hr->user, lr->user) ||	/* User has changed */
15851e72d8d2Sderaadt 	    strcmp (hr->mod, lr->mod) ||/* Module has changed */
15861e72d8d2Sderaadt 	    (working &&			/* If must match "workdir" */
15871e72d8d2Sderaadt 	     (strcmp (hr->dir, lr->dir) ||	/*    and the 1st parts or */
15881e72d8d2Sderaadt 	      strcmp (hr->end, lr->end))))	/*    the 2nd parts differ */
15891e72d8d2Sderaadt 
15901e72d8d2Sderaadt 	    return (1);
15911e72d8d2Sderaadt     }
15921e72d8d2Sderaadt     else if (modified)
15931e72d8d2Sderaadt     {
15941e72d8d2Sderaadt 	if (!last_entry ||		/* Don't want only last rec */
15951e72d8d2Sderaadt 	    !hr ||			/* Last entry is a "last entry" */
15961e72d8d2Sderaadt 	    strcmp (hr->repos, lr->repos) ||	/* Repository has changed */
15971e72d8d2Sderaadt 	    strcmp (hr->file, lr->file))/* File has changed */
15981e72d8d2Sderaadt 	    return (1);
15991e72d8d2Sderaadt 
16001e72d8d2Sderaadt 	if (working)
16011e72d8d2Sderaadt 	{				/* If must match "workdir" */
16021e72d8d2Sderaadt 	    if (strcmp (hr->dir, lr->dir) ||	/*    and the 1st parts or */
16031e72d8d2Sderaadt 		strcmp (hr->end, lr->end))	/*    the 2nd parts differ */
16041e72d8d2Sderaadt 		return (1);
16051e72d8d2Sderaadt 	}
16061e72d8d2Sderaadt     }
16071e72d8d2Sderaadt     else if (module_report)
16081e72d8d2Sderaadt     {
16091e72d8d2Sderaadt 	if (!last_entry ||		/* Don't want only last rec */
16101e72d8d2Sderaadt 	    !hr ||			/* Last entry is a "last entry" */
16111e72d8d2Sderaadt 	    strcmp (hr->mod, lr->mod) ||/* Module has changed */
16121e72d8d2Sderaadt 	    strcmp (hr->repos, lr->repos) ||	/* Repository has changed */
16131e72d8d2Sderaadt 	    strcmp (hr->file, lr->file))/* File has changed */
16141e72d8d2Sderaadt 	    return (1);
16151e72d8d2Sderaadt     }
16161e72d8d2Sderaadt     else
16171e72d8d2Sderaadt     {
16181e72d8d2Sderaadt 	/* "extract" and "tag_report" always print selected records. */
16191e72d8d2Sderaadt 	return (1);
16201e72d8d2Sderaadt     }
16211e72d8d2Sderaadt 
16221e72d8d2Sderaadt     return (0);
16231e72d8d2Sderaadt }
1624