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