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