1 /* 2 * Copyright (c) 1992, Brian Berliner and Jeff Polk 3 * Copyright (c) 1989-1992, Brian Berliner 4 * 5 * You may distribute under the terms of the GNU General Public License as 6 * specified in the README file that comes with the CVS source distribution. 7 * 8 * Print Log Information 9 * 10 * This line exists solely to test some pcl-cvs/ChangeLog stuff. You 11 * can delete it, if indeed it's still here when you read it. -Karl 12 * 13 * Prints the RCS "log" (rlog) information for the specified files. With no 14 * argument, prints the log information for all the files in the directory 15 * (recursive by default). 16 */ 17 18 #include "cvs.h" 19 20 /* This structure holds information parsed from the -r option. */ 21 22 struct option_revlist 23 { 24 /* The next -r option. */ 25 struct option_revlist *next; 26 /* The first revision to print. This is NULL if the range is 27 :rev, or if no revision is given. */ 28 char *first; 29 /* The last revision to print. This is NULL if the range is rev:, 30 or if no revision is given. If there is no colon, first and 31 last are the same. */ 32 char *last; 33 /* Nonzero if there was a trailing `.', which means to print only 34 the head revision of a branch. */ 35 int branchhead; 36 }; 37 38 /* This structure holds information derived from option_revlist given 39 a particular RCS file. */ 40 41 struct revlist 42 { 43 /* The next pair. */ 44 struct revlist *next; 45 /* The first numeric revision to print. */ 46 char *first; 47 /* The last numeric revision to print. */ 48 char *last; 49 /* The number of fields in these revisions (one more than 50 numdots). */ 51 int fields; 52 }; 53 54 /* This structure holds information parsed from the -d option. */ 55 56 struct datelist 57 { 58 /* The next date. */ 59 struct datelist *next; 60 /* The starting date. */ 61 char *start; 62 /* The ending date. */ 63 char *end; 64 /* Nonzero if the range is inclusive rather than exclusive. */ 65 int inclusive; 66 }; 67 68 /* This structure is used to pass information through start_recursion. */ 69 struct log_data 70 { 71 /* Nonzero if the -R option was given, meaning that only the name 72 of the RCS file should be printed. */ 73 int nameonly; 74 /* Nonzero if the -h option was given, meaning that only header 75 information should be printed. */ 76 int header; 77 /* Nonzero if the -t option was given, meaning that only the 78 header and the descriptive text should be printed. */ 79 int long_header; 80 /* Nonzero if the -N option was seen, meaning that tag information 81 should not be printed. */ 82 int notags; 83 /* Nonzero if the -b option was seen, meaning that only revisions 84 on the default branch should be printed. */ 85 int default_branch; 86 /* If not NULL, the value given for the -r option, which lists 87 sets of revisions to be printed. */ 88 struct option_revlist *revlist; 89 /* If not NULL, the date pairs given for the -d option, which 90 select date ranges to print. */ 91 struct datelist *datelist; 92 /* If not NULL, the single dates given for the -d option, which 93 select specific revisions to print based on a date. */ 94 struct datelist *singledatelist; 95 /* If not NULL, the list of states given for the -s option, which 96 only prints revisions of given states. */ 97 List *statelist; 98 /* If not NULL, the list of login names given for the -w option, 99 which only prints revisions checked in by given users. */ 100 List *authorlist; 101 }; 102 103 /* This structure is used to pass information through walklist. */ 104 struct log_data_and_rcs 105 { 106 struct log_data *log_data; 107 struct revlist *revlist; 108 RCSNode *rcs; 109 }; 110 111 static Dtype log_dirproc PROTO ((void *callerdat, char *dir, 112 char *repository, char *update_dir, 113 List *entries)); 114 static int log_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 115 static struct option_revlist *log_parse_revlist PROTO ((const char *)); 116 static void log_parse_date PROTO ((struct log_data *, const char *)); 117 static void log_parse_list PROTO ((List **, const char *)); 118 static struct revlist *log_expand_revlist PROTO ((RCSNode *, 119 struct option_revlist *, 120 int)); 121 static void log_free_revlist PROTO ((struct revlist *)); 122 static int log_version_requested PROTO ((struct log_data *, struct revlist *, 123 RCSNode *, RCSVers *)); 124 static int log_symbol PROTO ((Node *, void *)); 125 static int log_count PROTO ((Node *, void *)); 126 static int log_fix_singledate PROTO ((Node *, void *)); 127 static int log_count_print PROTO ((Node *, void *)); 128 static void log_tree PROTO ((struct log_data *, struct revlist *, 129 RCSNode *, const char *)); 130 static void log_abranch PROTO ((struct log_data *, struct revlist *, 131 RCSNode *, const char *)); 132 static void log_version PROTO ((struct log_data *, struct revlist *, 133 RCSNode *, RCSVers *, int)); 134 static int log_branch PROTO ((Node *, void *)); 135 static int version_compare PROTO ((const char *, const char *, int)); 136 137 static const char *const log_usage[] = 138 { 139 "Usage: %s %s [-lRhtNb] [-r[revisions]] [-d dates] [-s states]\n", 140 " [-w[logins]] [files...]\n", 141 "\t-l\tLocal directory only, no recursion.\n", 142 "\t-R\tOnly print name of RCS file.\n", 143 "\t-h\tOnly print header.\n", 144 "\t-t\tOnly print header and descriptive text.\n", 145 "\t-N\tDo not list tags.\n", 146 "\t-b\tOnly list revisions on the default branch.\n", 147 "\t-r[revisions]\tSpecify revision(s)s to list.\n", 148 "\t-d dates\tSpecify dates (D1<D2 for range, D for latest before).\n", 149 "\t-s states\tOnly list revisions with specified states.\n", 150 "\t-w[logins]\tOnly list revisions checked in by specified logins.\n", 151 "(Specify the --help global option for a list of other help options)\n", 152 NULL 153 }; 154 155 int 156 cvslog (argc, argv) 157 int argc; 158 char **argv; 159 { 160 int c; 161 int err = 0; 162 int local = 0; 163 struct log_data log_data; 164 struct option_revlist *rl, **prl; 165 166 if (argc == -1) 167 usage (log_usage); 168 169 memset (&log_data, 0, sizeof log_data); 170 171 optind = 0; 172 while ((c = getopt (argc, argv, "+bd:hlNRr::s:tw::")) != -1) 173 { 174 switch (c) 175 { 176 case 'b': 177 log_data.default_branch = 1; 178 break; 179 case 'd': 180 log_parse_date (&log_data, optarg); 181 break; 182 case 'h': 183 log_data.header = 1; 184 break; 185 case 'l': 186 local = 1; 187 break; 188 case 'N': 189 log_data.notags = 1; 190 break; 191 case 'R': 192 log_data.nameonly = 1; 193 break; 194 case 'r': 195 rl = log_parse_revlist (optarg); 196 for (prl = &log_data.revlist; 197 *prl != NULL; 198 prl = &(*prl)->next) 199 ; 200 *prl = rl; 201 break; 202 case 's': 203 log_parse_list (&log_data.statelist, optarg); 204 break; 205 case 't': 206 log_data.long_header = 1; 207 break; 208 case 'w': 209 if (optarg != NULL) 210 log_parse_list (&log_data.authorlist, optarg); 211 else 212 log_parse_list (&log_data.authorlist, getcaller ()); 213 break; 214 case '?': 215 default: 216 usage (log_usage); 217 break; 218 } 219 } 220 221 wrap_setup (); 222 223 #ifdef CLIENT_SUPPORT 224 if (client_active) 225 { 226 int i; 227 228 /* We're the local client. Fire up the remote server. */ 229 start_server (); 230 231 ign_setup (); 232 233 for (i = 1; i < argc && argv[i][0] == '-'; i++) 234 send_arg (argv[i]); 235 236 send_files (argc - i, argv + i, local, 0, SEND_NO_CONTENTS); 237 send_file_names (argc - i, argv + i, SEND_EXPAND_WILD); 238 239 send_to_server ("log\012", 0); 240 err = get_responses_and_close (); 241 return err; 242 } 243 #endif 244 245 err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc, 246 (DIRLEAVEPROC) NULL, (void *) &log_data, 247 argc - optind, argv + optind, local, 248 W_LOCAL | W_REPOS | W_ATTIC, 0, 1, 249 (char *) NULL, 1); 250 return (err); 251 } 252 253 /* 254 * Parse a revision list specification. 255 */ 256 257 static struct option_revlist * 258 log_parse_revlist (argstring) 259 const char *argstring; 260 { 261 char *copy; 262 struct option_revlist *ret, **pr; 263 264 /* Unfortunately, rlog accepts -r without an argument to mean that 265 latest revision on the default branch, so we must support that 266 for compatibility. */ 267 if (argstring == NULL) 268 { 269 ret = (struct option_revlist *) xmalloc (sizeof *ret); 270 ret->first = NULL; 271 ret->last = NULL; 272 ret->next = NULL; 273 ret->branchhead = 0; 274 return ret; 275 } 276 277 ret = NULL; 278 pr = &ret; 279 280 /* Copy the argument into memory so that we can change it. We 281 don't want to change the argument because, at least as of this 282 writing, we will use it if we send the arguments to the server. 283 We never bother to free up our copy. */ 284 copy = xstrdup (argstring); 285 while (copy != NULL) 286 { 287 char *comma; 288 char *cp; 289 char *first, *last; 290 struct option_revlist *r; 291 292 comma = strchr (copy, ','); 293 if (comma != NULL) 294 *comma++ = '\0'; 295 296 first = copy; 297 cp = strchr (copy, ':'); 298 if (cp == NULL) 299 last = copy; 300 else 301 { 302 *cp++ = '\0'; 303 last = cp; 304 } 305 306 if (*first == '\0') 307 first = NULL; 308 if (*last == '\0') 309 last = NULL; 310 311 r = (struct option_revlist *) xmalloc (sizeof *r); 312 r->next = NULL; 313 r->first = first; 314 r->last = last; 315 if (first != last 316 || first[strlen (first) - 1] != '.') 317 { 318 r->branchhead = 0; 319 } 320 else 321 { 322 r->branchhead = 1; 323 first[strlen (first) - 1] = '\0'; 324 } 325 326 *pr = r; 327 pr = &r->next; 328 329 copy = comma; 330 } 331 332 return ret; 333 } 334 335 /* 336 * Parse a date specification. 337 */ 338 static void 339 log_parse_date (log_data, argstring) 340 struct log_data *log_data; 341 const char *argstring; 342 { 343 char *orig_copy, *copy; 344 345 /* Copy the argument into memory so that we can change it. We 346 don't want to change the argument because, at least as of this 347 writing, we will use it if we send the arguments to the server. */ 348 copy = xstrdup (argstring); 349 orig_copy = copy; 350 while (copy != NULL) 351 { 352 struct datelist *nd, **pd; 353 char *cpend, *cp, *ds, *de; 354 355 nd = (struct datelist *) xmalloc (sizeof *nd); 356 357 cpend = strchr (copy, ';'); 358 if (cpend != NULL) 359 *cpend++ = '\0'; 360 361 pd = &log_data->datelist; 362 nd->inclusive = 0; 363 364 if ((cp = strchr (copy, '>')) != NULL) 365 { 366 *cp++ = '\0'; 367 if (*cp == '=') 368 { 369 ++cp; 370 nd->inclusive = 1; 371 } 372 ds = cp; 373 de = copy; 374 } 375 else if ((cp = strchr (copy, '<')) != NULL) 376 { 377 *cp++ = '\0'; 378 if (*cp == '=') 379 { 380 ++cp; 381 nd->inclusive = 1; 382 } 383 ds = copy; 384 de = cp; 385 } 386 else 387 { 388 ds = NULL; 389 de = copy; 390 pd = &log_data->singledatelist; 391 } 392 393 if (ds == NULL) 394 nd->start = NULL; 395 else if (*ds != '\0') 396 nd->start = Make_Date (ds); 397 else 398 { 399 /* 1970 was the beginning of time, as far as get_date and 400 Make_Date are concerned. FIXME: That is true only if time_t 401 is a POSIX-style time and there is nothing in ANSI that 402 mandates that. It would be cleaner to set a flag saying 403 whether or not there is a start date. */ 404 nd->start = Make_Date ("1/1/1970 UTC"); 405 } 406 407 if (*de != '\0') 408 nd->end = Make_Date (de); 409 else 410 { 411 /* We want to set the end date to some time sufficiently far 412 in the future to pick up all revisions that have been 413 created since the specified date and the time `cvs log' 414 completes. FIXME: The date in question only makes sense 415 if time_t is a POSIX-style time and it is 32 bits 416 and signed. We should instead be setting a flag saying 417 whether or not there is an end date. Note that using 418 something like "next week" would break the testsuite (and, 419 perhaps less importantly, loses if the clock is set grossly 420 wrong). */ 421 nd->end = Make_Date ("2038-01-01"); 422 } 423 424 nd->next = *pd; 425 *pd = nd; 426 427 copy = cpend; 428 } 429 430 free (orig_copy); 431 } 432 433 /* 434 * Parse a comma separated list of items, and add each one to *PLIST. 435 */ 436 static void 437 log_parse_list (plist, argstring) 438 List **plist; 439 const char *argstring; 440 { 441 while (1) 442 { 443 Node *p; 444 char *cp; 445 446 p = getnode (); 447 448 cp = strchr (argstring, ','); 449 if (cp == NULL) 450 p->key = xstrdup (argstring); 451 else 452 { 453 size_t len; 454 455 len = cp - argstring; 456 p->key = xmalloc (len + 1); 457 strncpy (p->key, argstring, len); 458 p->key[len + 1] = '\0'; 459 } 460 461 if (*plist == NULL) 462 *plist = getlist (); 463 if (addnode (*plist, p) != 0) 464 freenode (p); 465 466 if (cp == NULL) 467 break; 468 469 argstring = cp + 1; 470 } 471 } 472 473 static int printlock_proc PROTO ((Node *, void *)); 474 475 static int 476 printlock_proc (lock, foo) 477 Node *lock; 478 void *foo; 479 { 480 cvs_output ("\n\t", 2); 481 cvs_output (lock->data, 0); 482 cvs_output (": ", 2); 483 cvs_output (lock->key, 0); 484 return 0; 485 } 486 487 /* 488 * Do an rlog on a file 489 */ 490 static int 491 log_fileproc (callerdat, finfo) 492 void *callerdat; 493 struct file_info *finfo; 494 { 495 struct log_data *log_data = (struct log_data *) callerdat; 496 Node *p; 497 RCSNode *rcsfile; 498 char buf[50]; 499 struct revlist *revlist; 500 struct log_data_and_rcs log_data_and_rcs; 501 502 if ((rcsfile = finfo->rcs) == NULL) 503 { 504 /* no rcs file. What *do* we know about this file? */ 505 p = findnode (finfo->entries, finfo->file); 506 if (p != NULL) 507 { 508 Entnode *e; 509 510 e = (Entnode *) p->data; 511 if (e->version[0] == '0' && e->version[1] == '\0') 512 { 513 if (!really_quiet) 514 error (0, 0, "%s has been added, but not committed", 515 finfo->file); 516 return(0); 517 } 518 } 519 520 if (!really_quiet) 521 error (0, 0, "nothing known about %s", finfo->file); 522 523 return (1); 524 } 525 526 if (log_data->nameonly) 527 { 528 cvs_output (rcsfile->path, 0); 529 cvs_output ("\n", 1); 530 return 0; 531 } 532 533 /* We will need all the information in the RCS file. */ 534 RCS_fully_parse (rcsfile); 535 536 /* Turn any symbolic revisions in the revision list into numeric 537 revisions. */ 538 revlist = log_expand_revlist (rcsfile, log_data->revlist, 539 log_data->default_branch); 540 541 /* The output here is intended to be exactly compatible with the 542 output of rlog. I'm not sure whether this code should be here 543 or in rcs.c; I put it here because it is specific to the log 544 function, even though it uses information gathered by the 545 functions in rcs.c. */ 546 547 cvs_output ("\n", 1); 548 549 cvs_output ("RCS file: ", 0); 550 cvs_output (rcsfile->path, 0); 551 552 cvs_output ("\nWorking file: ", 0); 553 if (finfo->update_dir[0] == '\0') 554 cvs_output (finfo->file, 0); 555 else 556 { 557 cvs_output (finfo->update_dir, 0); 558 cvs_output ("/", 0); 559 cvs_output (finfo->file, 0); 560 561 } 562 563 cvs_output ("\nhead:", 0); 564 if (rcsfile->head != NULL) 565 { 566 cvs_output (" ", 1); 567 cvs_output (rcsfile->head, 0); 568 } 569 570 cvs_output ("\nbranch:", 0); 571 if (rcsfile->branch != NULL) 572 { 573 cvs_output (" ", 1); 574 cvs_output (rcsfile->branch, 0); 575 } 576 577 cvs_output ("\nlocks:", 0); 578 if (rcsfile->strict_locks) 579 cvs_output (" strict", 0); 580 walklist (RCS_getlocks (rcsfile), printlock_proc, NULL); 581 582 cvs_output ("\naccess list:", 0); 583 if (rcsfile->access != NULL) 584 { 585 const char *cp; 586 587 cp = rcsfile->access; 588 while (*cp != '\0') 589 { 590 const char *cp2; 591 592 cvs_output ("\n\t", 2); 593 cp2 = cp; 594 while (! isspace ((unsigned char) *cp2) && *cp2 != '\0') 595 ++cp2; 596 cvs_output (cp, cp2 - cp); 597 cp = cp2; 598 while (isspace ((unsigned char) *cp) && *cp != '\0') 599 ++cp; 600 } 601 } 602 603 if (! log_data->notags) 604 { 605 List *syms; 606 607 cvs_output ("\nsymbolic names:", 0); 608 syms = RCS_symbols (rcsfile); 609 walklist (syms, log_symbol, NULL); 610 } 611 612 cvs_output ("\nkeyword substitution: ", 0); 613 if (rcsfile->expand == NULL) 614 cvs_output ("kv", 2); 615 else 616 cvs_output (rcsfile->expand, 0); 617 618 cvs_output ("\ntotal revisions: ", 0); 619 sprintf (buf, "%d", walklist (rcsfile->versions, log_count, NULL)); 620 cvs_output (buf, 0); 621 622 if (! log_data->header && ! log_data->long_header) 623 { 624 cvs_output (";\tselected revisions: ", 0); 625 626 log_data_and_rcs.log_data = log_data; 627 log_data_and_rcs.revlist = revlist; 628 log_data_and_rcs.rcs = rcsfile; 629 630 /* If any single dates were specified, we need to identify the 631 revisions they select. Each one selects the single 632 revision, which is otherwise selected, of that date or 633 earlier. The log_fix_singledate routine will fill in the 634 start date for each specific revision. */ 635 if (log_data->singledatelist != NULL) 636 walklist (rcsfile->versions, log_fix_singledate, 637 (void *) &log_data_and_rcs); 638 639 sprintf (buf, "%d", walklist (rcsfile->versions, log_count_print, 640 (void *) &log_data_and_rcs)); 641 cvs_output (buf, 0); 642 } 643 644 cvs_output ("\n", 1); 645 646 if (! log_data->header || log_data->long_header) 647 { 648 cvs_output ("description:\n", 0); 649 if (rcsfile->desc != NULL) 650 cvs_output (rcsfile->desc, 0); 651 } 652 653 if (! log_data->header && ! log_data->long_header && rcsfile->head != NULL) 654 { 655 p = findnode (rcsfile->versions, rcsfile->head); 656 if (p == NULL) 657 error (1, 0, "can not find head revision in `%s'", 658 finfo->fullname); 659 while (p != NULL) 660 { 661 RCSVers *vers; 662 663 vers = (RCSVers *) p->data; 664 log_version (log_data, revlist, rcsfile, vers, 1); 665 if (vers->next == NULL) 666 p = NULL; 667 else 668 { 669 p = findnode (rcsfile->versions, vers->next); 670 if (p == NULL) 671 error (1, 0, "can not find next revision `%s' in `%s'", 672 vers->next, finfo->fullname); 673 } 674 } 675 676 log_tree (log_data, revlist, rcsfile, rcsfile->head); 677 } 678 679 cvs_output("\ 680 =============================================================================\n", 681 0); 682 683 /* Free up the new revlist and restore the old one. */ 684 log_free_revlist (revlist); 685 686 /* If singledatelist is not NULL, free up the start dates we added 687 to it. */ 688 if (log_data->singledatelist != NULL) 689 { 690 struct datelist *d; 691 692 for (d = log_data->singledatelist; d != NULL; d = d->next) 693 { 694 if (d->start != NULL) 695 free (d->start); 696 d->start = NULL; 697 } 698 } 699 700 return 0; 701 } 702 703 /* 704 * Fix up a revision list in order to compare it against versions. 705 * Expand any symbolic revisions. 706 */ 707 static struct revlist * 708 log_expand_revlist (rcs, revlist, default_branch) 709 RCSNode *rcs; 710 struct option_revlist *revlist; 711 int default_branch; 712 { 713 struct option_revlist *r; 714 struct revlist *ret, **pr; 715 716 ret = NULL; 717 pr = &ret; 718 for (r = revlist; r != NULL; r = r->next) 719 { 720 struct revlist *nr; 721 722 nr = (struct revlist *) xmalloc (sizeof *nr); 723 724 if (r->first == NULL && r->last == NULL) 725 { 726 /* If both first and last are NULL, it means that we want 727 just the head of the default branch, which is RCS_head. */ 728 nr->first = RCS_head (rcs); 729 nr->last = xstrdup (nr->first); 730 nr->fields = numdots (nr->first) + 1; 731 } 732 else if (r->branchhead) 733 { 734 char *branch; 735 736 /* Print just the head of the branch. */ 737 if (isdigit ((unsigned char) r->first[0])) 738 nr->first = RCS_getbranch (rcs, r->first, 1); 739 else 740 { 741 branch = RCS_whatbranch (rcs, r->first); 742 if (branch == NULL) 743 { 744 error (0, 0, "warning: `%s' is not a branch in `%s'", 745 r->first, rcs->path); 746 free (nr); 747 continue; 748 } 749 nr->first = RCS_getbranch (rcs, branch, 1); 750 free (branch); 751 } 752 if (nr->first == NULL) 753 { 754 error (0, 0, "warning: no revision `%s' in `%s'", 755 r->first, rcs->path); 756 free (nr); 757 continue; 758 } 759 nr->last = xstrdup (nr->first); 760 nr->fields = numdots (nr->first) + 1; 761 } 762 else 763 { 764 if (r->first == NULL || isdigit ((unsigned char) r->first[0])) 765 nr->first = xstrdup (r->first); 766 else 767 { 768 if (RCS_nodeisbranch (rcs, r->first)) 769 nr->first = RCS_whatbranch (rcs, r->first); 770 else 771 nr->first = RCS_gettag (rcs, r->first, 1, (int *) NULL); 772 if (nr->first == NULL) 773 { 774 error (0, 0, "warning: no revision `%s' in `%s'", 775 r->first, rcs->path); 776 free (nr); 777 continue; 778 } 779 } 780 781 if (r->last == r->first) 782 nr->last = xstrdup (nr->first); 783 else if (r->last == NULL || isdigit ((unsigned char) r->last[0])) 784 nr->last = xstrdup (r->last); 785 else 786 { 787 if (RCS_nodeisbranch (rcs, r->last)) 788 nr->last = RCS_whatbranch (rcs, r->last); 789 else 790 nr->last = RCS_gettag (rcs, r->last, 1, (int *) NULL); 791 if (nr->last == NULL) 792 { 793 error (0, 0, "warning: no revision `%s' in `%s'", 794 r->last, rcs->path); 795 if (nr->first != NULL) 796 free (nr->first); 797 free (nr); 798 continue; 799 } 800 } 801 802 /* Process the revision numbers the same way that rlog 803 does. This code is a bit cryptic for my tastes, but 804 keeping the same implementation as rlog ensures a 805 certain degree of compatibility. */ 806 if (r->first == NULL) 807 { 808 nr->fields = numdots (nr->last) + 1; 809 if (nr->fields < 2) 810 nr->first = xstrdup (".0"); 811 else 812 { 813 char *cp; 814 815 nr->first = xstrdup (nr->last); 816 cp = strrchr (nr->first, '.'); 817 strcpy (cp, ".0"); 818 } 819 } 820 else if (r->last == NULL) 821 { 822 nr->fields = numdots (nr->first) + 1; 823 nr->last = xstrdup (nr->first); 824 if (nr->fields < 2) 825 nr->last[0] = '\0'; 826 else 827 { 828 char *cp; 829 830 cp = strrchr (nr->last, '.'); 831 *cp = '\0'; 832 } 833 } 834 else 835 { 836 nr->fields = numdots (nr->first) + 1; 837 if (nr->fields != numdots (nr->last) + 1 838 || (nr->fields > 2 839 && version_compare (nr->first, nr->last, 840 nr->fields - 1) != 0)) 841 { 842 error (0, 0, 843 "invalid branch or revision pair %s:%s in `%s'", 844 r->first, r->last, rcs->path); 845 free (nr->first); 846 free (nr->last); 847 free (nr); 848 continue; 849 } 850 if (version_compare (nr->first, nr->last, nr->fields) > 0) 851 { 852 char *tmp; 853 854 tmp = nr->first; 855 nr->first = nr->last; 856 nr->last = tmp; 857 } 858 } 859 } 860 861 nr->next = NULL; 862 *pr = nr; 863 pr = &nr->next; 864 } 865 866 /* If the default branch was requested, add a revlist entry for 867 it. This is how rlog handles this option. */ 868 if (default_branch 869 && (rcs->head != NULL || rcs->branch != NULL)) 870 { 871 struct revlist *nr; 872 873 nr = (struct revlist *) xmalloc (sizeof *nr); 874 if (rcs->branch != NULL) 875 nr->first = xstrdup (rcs->branch); 876 else 877 { 878 char *cp; 879 880 nr->first = xstrdup (rcs->head); 881 cp = strrchr (nr->first, '.'); 882 *cp = '\0'; 883 } 884 nr->last = xstrdup (nr->first); 885 nr->fields = numdots (nr->first) + 1; 886 887 nr->next = NULL; 888 *pr = nr; 889 } 890 891 return ret; 892 } 893 894 /* 895 * Free a revlist created by log_expand_revlist. 896 */ 897 static void 898 log_free_revlist (revlist) 899 struct revlist *revlist; 900 { 901 struct revlist *r; 902 903 r = revlist; 904 while (r != NULL) 905 { 906 struct revlist *next; 907 908 if (r->first != NULL) 909 free (r->first); 910 if (r->last != NULL) 911 free (r->last); 912 next = r->next; 913 free (r); 914 r = next; 915 } 916 } 917 918 /* 919 * Return nonzero if a revision should be printed, based on the 920 * options provided. 921 */ 922 static int 923 log_version_requested (log_data, revlist, rcs, vnode) 924 struct log_data *log_data; 925 struct revlist *revlist; 926 RCSNode *rcs; 927 RCSVers *vnode; 928 { 929 /* Handle the list of states from the -s option. */ 930 if (log_data->statelist != NULL 931 && findnode (log_data->statelist, vnode->state) == NULL) 932 { 933 return 0; 934 } 935 936 /* Handle the list of authors from the -w option. */ 937 if (log_data->authorlist != NULL) 938 { 939 if (vnode->author != NULL 940 && findnode (log_data->authorlist, vnode->author) == NULL) 941 { 942 return 0; 943 } 944 } 945 946 /* rlog considers all the -d options together when it decides 947 whether to print a revision, so we must be compatible. */ 948 if (log_data->datelist != NULL || log_data->singledatelist != NULL) 949 { 950 struct datelist *d; 951 952 for (d = log_data->datelist; d != NULL; d = d->next) 953 { 954 int cmp; 955 956 cmp = RCS_datecmp (vnode->date, d->start); 957 if (cmp > 0 || (cmp == 0 && d->inclusive)) 958 { 959 cmp = RCS_datecmp (vnode->date, d->end); 960 if (cmp < 0 || (cmp == 0 && d->inclusive)) 961 break; 962 } 963 } 964 965 if (d == NULL) 966 { 967 /* Look through the list of specific dates. We want to 968 select the revision with the exact date found in the 969 start field. The commit code ensures that it is 970 impossible to check in multiple revisions of a single 971 file in a single second, so checking the date this way 972 should never select more than one revision. */ 973 for (d = log_data->singledatelist; d != NULL; d = d->next) 974 { 975 if (d->start != NULL 976 && RCS_datecmp (vnode->date, d->start) == 0) 977 { 978 break; 979 } 980 } 981 982 if (d == NULL) 983 return 0; 984 } 985 } 986 987 /* If the -r or -b options were used, REVLIST will be non NULL, 988 and we print the union of the specified revisions. */ 989 if (revlist != NULL) 990 { 991 char *v; 992 int vfields; 993 struct revlist *r; 994 995 /* This code is taken from rlog. */ 996 v = vnode->version; 997 vfields = numdots (v) + 1; 998 for (r = revlist; r != NULL; r = r->next) 999 { 1000 if (vfields == r->fields + (r->fields & 1) 1001 && version_compare (v, r->first, r->fields) >= 0 1002 && version_compare (v, r->last, r->fields) <= 0) 1003 { 1004 return 1; 1005 } 1006 } 1007 1008 /* If we get here, then the -b and/or the -r option was used, 1009 but did not match this revision, so we reject it. */ 1010 1011 return 0; 1012 } 1013 1014 /* By default, we print all revisions. */ 1015 return 1; 1016 } 1017 1018 /* 1019 * Output a single symbol. This is called via walklist. 1020 */ 1021 /*ARGSUSED*/ 1022 static int 1023 log_symbol (p, closure) 1024 Node *p; 1025 void *closure; 1026 { 1027 cvs_output ("\n\t", 2); 1028 cvs_output (p->key, 0); 1029 cvs_output (": ", 2); 1030 cvs_output (p->data, 0); 1031 return 0; 1032 } 1033 1034 /* 1035 * Count the number of entries on a list. This is called via walklist. 1036 */ 1037 /*ARGSUSED*/ 1038 static int 1039 log_count (p, closure) 1040 Node *p; 1041 void *closure; 1042 { 1043 return 1; 1044 } 1045 1046 /* 1047 * Sort out a single date specification by narrowing down the date 1048 * until we find the specific selected revision. 1049 */ 1050 static int 1051 log_fix_singledate (p, closure) 1052 Node *p; 1053 void *closure; 1054 { 1055 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; 1056 Node *pv; 1057 RCSVers *vnode; 1058 struct datelist *holdsingle, *holddate; 1059 int requested; 1060 1061 pv = findnode (data->rcs->versions, p->key); 1062 if (pv == NULL) 1063 error (1, 0, "missing version `%s' in RCS file `%s'", 1064 p->key, data->rcs->path); 1065 vnode = (RCSVers *) pv->data; 1066 1067 /* We are only interested if this revision passes any other tests. 1068 Temporarily clear log_data->singledatelist to avoid confusing 1069 log_version_requested. We also clear log_data->datelist, 1070 because rlog considers all the -d options together. We don't 1071 want to reject a revision because it does not match a date pair 1072 if we are going to select it on the basis of the singledate. */ 1073 holdsingle = data->log_data->singledatelist; 1074 data->log_data->singledatelist = NULL; 1075 holddate = data->log_data->datelist; 1076 data->log_data->datelist = NULL; 1077 requested = log_version_requested (data->log_data, data->revlist, 1078 data->rcs, vnode); 1079 data->log_data->singledatelist = holdsingle; 1080 data->log_data->datelist = holddate; 1081 1082 if (requested) 1083 { 1084 struct datelist *d; 1085 1086 /* For each single date, if this revision is before the 1087 specified date, but is closer than the previously selected 1088 revision, select it instead. */ 1089 for (d = data->log_data->singledatelist; d != NULL; d = d->next) 1090 { 1091 if (RCS_datecmp (vnode->date, d->end) <= 0 1092 && (d->start == NULL 1093 || RCS_datecmp (vnode->date, d->start) > 0)) 1094 { 1095 if (d->start != NULL) 1096 free (d->start); 1097 d->start = xstrdup (vnode->date); 1098 } 1099 } 1100 } 1101 1102 return 0; 1103 } 1104 1105 /* 1106 * Count the number of revisions we are going to print. 1107 */ 1108 static int 1109 log_count_print (p, closure) 1110 Node *p; 1111 void *closure; 1112 { 1113 struct log_data_and_rcs *data = (struct log_data_and_rcs *) closure; 1114 Node *pv; 1115 1116 pv = findnode (data->rcs->versions, p->key); 1117 if (pv == NULL) 1118 error (1, 0, "missing version `%s' in RCS file `%s'", 1119 p->key, data->rcs->path); 1120 if (log_version_requested (data->log_data, data->revlist, data->rcs, 1121 (RCSVers *) pv->data)) 1122 return 1; 1123 else 1124 return 0; 1125 } 1126 1127 /* 1128 * Print the list of changes, not including the trunk, in reverse 1129 * order for each branch. 1130 */ 1131 static void 1132 log_tree (log_data, revlist, rcs, ver) 1133 struct log_data *log_data; 1134 struct revlist *revlist; 1135 RCSNode *rcs; 1136 const char *ver; 1137 { 1138 Node *p; 1139 RCSVers *vnode; 1140 1141 p = findnode (rcs->versions, ver); 1142 if (p == NULL) 1143 error (1, 0, "missing version `%s' in RCS file `%s'", 1144 ver, rcs->path); 1145 vnode = (RCSVers *) p->data; 1146 if (vnode->next != NULL) 1147 log_tree (log_data, revlist, rcs, vnode->next); 1148 if (vnode->branches != NULL) 1149 { 1150 Node *head, *branch; 1151 1152 /* We need to do the branches in reverse order. This breaks 1153 the List abstraction, but so does most of the branch 1154 manipulation in rcs.c. */ 1155 head = vnode->branches->list; 1156 for (branch = head->prev; branch != head; branch = branch->prev) 1157 { 1158 log_abranch (log_data, revlist, rcs, branch->key); 1159 log_tree (log_data, revlist, rcs, branch->key); 1160 } 1161 } 1162 } 1163 1164 /* 1165 * Log the changes for a branch, in reverse order. 1166 */ 1167 static void 1168 log_abranch (log_data, revlist, rcs, ver) 1169 struct log_data *log_data; 1170 struct revlist *revlist; 1171 RCSNode *rcs; 1172 const char *ver; 1173 { 1174 Node *p; 1175 RCSVers *vnode; 1176 1177 p = findnode (rcs->versions, ver); 1178 if (p == NULL) 1179 error (1, 0, "missing version `%s' in RCS file `%s'", 1180 ver, rcs->path); 1181 vnode = (RCSVers *) p->data; 1182 if (vnode->next != NULL) 1183 log_abranch (log_data, revlist, rcs, vnode->next); 1184 log_version (log_data, revlist, rcs, vnode, 0); 1185 } 1186 1187 /* 1188 * Print the log output for a single version. 1189 */ 1190 static void 1191 log_version (log_data, revlist, rcs, ver, trunk) 1192 struct log_data *log_data; 1193 struct revlist *revlist; 1194 RCSNode *rcs; 1195 RCSVers *ver; 1196 int trunk; 1197 { 1198 Node *p; 1199 int year, mon, mday, hour, min, sec; 1200 char buf[100]; 1201 Node *padd, *pdel; 1202 1203 if (! log_version_requested (log_data, revlist, rcs, ver)) 1204 return; 1205 1206 cvs_output ("----------------------------\nrevision ", 0); 1207 cvs_output (ver->version, 0); 1208 1209 p = findnode (RCS_getlocks (rcs), ver->version); 1210 if (p != NULL) 1211 { 1212 cvs_output ("\tlocked by: ", 0); 1213 cvs_output (p->data, 0); 1214 cvs_output (";", 1); 1215 } 1216 1217 cvs_output ("\ndate: ", 0); 1218 (void) sscanf (ver->date, SDATEFORM, &year, &mon, &mday, &hour, &min, 1219 &sec); 1220 if (year < 1900) 1221 year += 1900; 1222 sprintf (buf, "%04d/%02d/%02d %02d:%02d:%02d", year, mon, mday, 1223 hour, min, sec); 1224 cvs_output (buf, 0); 1225 1226 cvs_output ("; author: ", 0); 1227 cvs_output (ver->author, 0); 1228 1229 cvs_output ("; state: ", 0); 1230 cvs_output (ver->state, 0); 1231 cvs_output (";", 1); 1232 1233 if (! trunk) 1234 { 1235 padd = findnode (ver->other, ";add"); 1236 pdel = findnode (ver->other, ";delete"); 1237 } 1238 else if (ver->next == NULL) 1239 { 1240 padd = NULL; 1241 pdel = NULL; 1242 } 1243 else 1244 { 1245 Node *nextp; 1246 RCSVers *nextver; 1247 1248 nextp = findnode (rcs->versions, ver->next); 1249 if (nextp == NULL) 1250 error (1, 0, "missing version `%s' in `%s'", ver->next, 1251 rcs->path); 1252 nextver = (RCSVers *) nextp->data; 1253 pdel = findnode (nextver->other, ";add"); 1254 padd = findnode (nextver->other, ";delete"); 1255 } 1256 1257 if (padd != NULL) 1258 { 1259 cvs_output (" lines: +", 0); 1260 cvs_output (padd->data, 0); 1261 cvs_output (" -", 2); 1262 cvs_output (pdel->data, 0); 1263 } 1264 1265 if (ver->branches != NULL) 1266 { 1267 cvs_output ("\nbranches:", 0); 1268 walklist (ver->branches, log_branch, (void *) NULL); 1269 } 1270 1271 cvs_output ("\n", 1); 1272 1273 p = findnode (ver->other, "log"); 1274 /* The p->date == NULL case is the normal one for an empty log 1275 message (rcs-14 in sanity.sh). I don't think the case where 1276 p->data is "" can happen (getrcskey in rcs.c checks for an 1277 empty string and set the value to NULL in that case). My guess 1278 would be the p == NULL case would mean an RCS file which was 1279 missing the "log" keyword (which is illegal according to 1280 rcsfile.5). */ 1281 if (p == NULL || p->data == NULL || p->data[0] == '\0') 1282 cvs_output ("*** empty log message ***\n", 0); 1283 else 1284 { 1285 /* FIXME: Technically, the log message could contain a null 1286 byte. */ 1287 cvs_output (p->data, 0); 1288 if (p->data[strlen (p->data) - 1] != '\n') 1289 cvs_output ("\n", 1); 1290 } 1291 } 1292 1293 /* 1294 * Output a branch version. This is called via walklist. 1295 */ 1296 /*ARGSUSED*/ 1297 static int 1298 log_branch (p, closure) 1299 Node *p; 1300 void *closure; 1301 { 1302 cvs_output (" ", 2); 1303 if ((numdots (p->key) & 1) == 0) 1304 cvs_output (p->key, 0); 1305 else 1306 { 1307 char *f, *cp; 1308 1309 f = xstrdup (p->key); 1310 cp = strrchr (f, '.'); 1311 *cp = '\0'; 1312 cvs_output (f, 0); 1313 free (f); 1314 } 1315 cvs_output (";", 1); 1316 return 0; 1317 } 1318 1319 /* 1320 * Print a warm fuzzy message 1321 */ 1322 /* ARGSUSED */ 1323 static Dtype 1324 log_dirproc (callerdat, dir, repository, update_dir, entries) 1325 void *callerdat; 1326 char *dir; 1327 char *repository; 1328 char *update_dir; 1329 List *entries; 1330 { 1331 if (!isdir (dir)) 1332 return (R_SKIP_ALL); 1333 1334 if (!quiet) 1335 error (0, 0, "Logging %s", update_dir); 1336 return (R_PROCESS); 1337 } 1338 1339 /* 1340 * Compare versions. This is taken from RCS compartial. 1341 */ 1342 static int 1343 version_compare (v1, v2, len) 1344 const char *v1; 1345 const char *v2; 1346 int len; 1347 { 1348 while (1) 1349 { 1350 int d1, d2, r; 1351 1352 if (*v1 == '\0') 1353 return 1; 1354 if (*v2 == '\0') 1355 return -1; 1356 1357 while (*v1 == '0') 1358 ++v1; 1359 for (d1 = 0; isdigit ((unsigned char) v1[d1]); ++d1) 1360 ; 1361 1362 while (*v2 == '0') 1363 ++v2; 1364 for (d2 = 0; isdigit ((unsigned char) v2[d2]); ++d2) 1365 ; 1366 1367 if (d1 != d2) 1368 return d1 < d2 ? -1 : 1; 1369 1370 r = memcmp (v1, v2, d1); 1371 if (r != 0) 1372 return r; 1373 1374 --len; 1375 if (len == 0) 1376 return 0; 1377 1378 v1 += d1; 1379 v2 += d1; 1380 1381 if (*v1 == '.') 1382 ++v1; 1383 if (*v2 == '.') 1384 ++v2; 1385 } 1386 } 1387