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