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