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