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