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