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