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