| 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 | } |