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