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