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