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