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