1 /* 2 * Copyright (c) 1992, Brian Berliner and Jeff Polk 3 * Copyright (c) 1989-1992, Brian Berliner 4 * 5 * You may distribute under the terms of the GNU General Public License as 6 * specified in the README file that comes with the CVS source distribution. 7 * 8 * Administration ("cvs admin") 9 * 10 */ 11 12 #include "cvs.h" 13 #ifdef CVS_ADMIN_GROUP 14 #include <grp.h> 15 #endif 16 #include <assert.h> 17 18 static Dtype admin_dirproc PROTO ((void *callerdat, char *dir, 19 char *repos, char *update_dir, 20 List *entries)); 21 static int admin_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 22 23 static const char *const admin_usage[] = 24 { 25 "Usage: %s %s rcs-options files...\n", 26 "(Specify the --help global option for a list of other help options)\n", 27 NULL 28 }; 29 30 /* This structure is used to pass information through start_recursion. */ 31 struct admin_data 32 { 33 /* Set default branch (-b). It is "-b" followed by the value 34 given, or NULL if not specified, or merely "-b" if -b is 35 specified without a value. */ 36 char *branch; 37 38 /* Set comment leader (-c). It is "-c" followed by the value 39 given, or NULL if not specified. The comment leader is 40 relevant only for old versions of RCS, but we let people set it 41 anyway. */ 42 char *comment; 43 44 /* Set strict locking (-L). */ 45 int set_strict; 46 47 /* Set nonstrict locking (-U). */ 48 int set_nonstrict; 49 50 /* Delete revisions (-o). It is "-o" followed by the value specified. */ 51 char *delete_revs; 52 53 /* Keyword substitution mode (-k), e.g. "-kb". */ 54 char *kflag; 55 56 /* Description (-t). See sanity.sh for various moanings about 57 files and stdin and such. "" if -t specified without an 58 argument. It is "-t" followed by the argument. */ 59 char *desc; 60 61 /* Interactive (-I). Problematic with client/server. */ 62 int interactive; 63 64 /* Quiet (-q). Not the same as the global -q option, which is a bit 65 on the confusing side, perhaps. */ 66 int quiet; 67 68 /* This is the cheesy part. It is a vector with the options which 69 we don't deal with above (e.g. "-afoo" "-abar,baz"). In the future 70 this presumably will be replaced by other variables which break 71 out the data in a more convenient fashion. AV as well as each of 72 the strings it points to is malloc'd. */ 73 int ac; 74 char **av; 75 int av_alloc; 76 }; 77 78 /* Add an argument. OPT is the option letter, e.g. 'a'. ARG is the 79 argument to that option, or NULL if omitted (whether NULL can actually 80 happen depends on whether the option was specified as optional to 81 getopt). */ 82 static void 83 arg_add (dat, opt, arg) 84 struct admin_data *dat; 85 int opt; 86 char *arg; 87 { 88 char *newelt = xmalloc ((arg == NULL ? 0 : strlen (arg)) + 3); 89 strcpy (newelt, "-"); 90 newelt[1] = opt; 91 if (arg == NULL) 92 newelt[2] = '\0'; 93 else 94 strcpy (newelt + 2, arg); 95 96 if (dat->av_alloc == 0) 97 { 98 dat->av_alloc = 1; 99 dat->av = (char **) xmalloc (dat->av_alloc * sizeof (*dat->av)); 100 } 101 else if (dat->ac >= dat->av_alloc) 102 { 103 dat->av_alloc *= 2; 104 dat->av = (char **) xrealloc (dat->av, 105 dat->av_alloc * sizeof (*dat->av)); 106 } 107 dat->av[dat->ac++] = newelt; 108 } 109 110 int 111 admin (argc, argv) 112 int argc; 113 char **argv; 114 { 115 int err; 116 #ifdef CVS_ADMIN_GROUP 117 struct group *grp; 118 struct group *getgrnam(); 119 #endif 120 struct admin_data admin_data; 121 int c; 122 int i; 123 124 if (argc <= 1) 125 usage (admin_usage); 126 127 #ifdef CVS_ADMIN_GROUP 128 grp = getgrnam(CVS_ADMIN_GROUP); 129 /* skip usage right check if group CVS_ADMIN_GROUP does not exist */ 130 if (grp != NULL) 131 { 132 char *me = getcaller(); 133 char **grnam = grp->gr_mem; 134 int denied = 1; 135 136 while (*grnam) 137 { 138 if (strcmp(*grnam, me) == 0) 139 { 140 denied = 0; 141 break; 142 } 143 grnam++; 144 } 145 146 if (denied) 147 error (1, 0, "usage is restricted to members of the group %s", 148 CVS_ADMIN_GROUP); 149 } 150 #endif 151 152 wrap_setup (); 153 154 memset (&admin_data, 0, sizeof admin_data); 155 156 /* TODO: get rid of `-' switch notation in admin_data. For 157 example, admin_data->branch should be not `-bfoo' but simply `foo'. */ 158 159 optind = 0; 160 while ((c = getopt (argc, argv, 161 "+ib::c:a:A:e:l::u::LUn:N:m:o:s:t::IqxV:k:")) != -1) 162 { 163 switch (c) 164 { 165 case 'i': 166 /* This has always been documented as useless in cvs.texinfo 167 and it really is--admin_fileproc silently does nothing 168 if vers->vn_user is NULL. */ 169 error (0, 0, "the -i option to admin is not supported"); 170 error (0, 0, "run add or import to create an RCS file"); 171 goto usage_error; 172 173 case 'b': 174 if (admin_data.branch != NULL) 175 { 176 error (0, 0, "duplicate 'b' option"); 177 goto usage_error; 178 } 179 if (optarg == NULL) 180 admin_data.branch = xstrdup ("-b"); 181 else 182 { 183 admin_data.branch = xmalloc (strlen (optarg) + 5); 184 strcpy (admin_data.branch, "-b"); 185 strcat (admin_data.branch, optarg); 186 } 187 break; 188 189 case 'c': 190 if (admin_data.comment != NULL) 191 { 192 error (0, 0, "duplicate 'c' option"); 193 goto usage_error; 194 } 195 admin_data.comment = xmalloc (strlen (optarg) + 5); 196 strcpy (admin_data.comment, "-c"); 197 strcat (admin_data.comment, optarg); 198 break; 199 200 case 'a': 201 arg_add (&admin_data, 'a', optarg); 202 break; 203 204 case 'A': 205 /* In the client/server case, this is cheesy because 206 we just pass along the name of the RCS file, which 207 then will want to exist on the server. This is 208 accidental; having the client specify a pathname on 209 the server is not a design feature of the protocol. */ 210 arg_add (&admin_data, 'A', optarg); 211 break; 212 213 case 'e': 214 arg_add (&admin_data, 'e', optarg); 215 break; 216 217 case 'l': 218 /* Note that multiple -l options are legal. */ 219 arg_add (&admin_data, 'l', optarg); 220 break; 221 222 case 'u': 223 /* Note that multiple -u options are legal. */ 224 arg_add (&admin_data, 'u', optarg); 225 break; 226 227 case 'L': 228 /* Probably could also complain if -L is specified multiple 229 times, although RCS doesn't and I suppose it is reasonable 230 just to have it mean the same as a single -L. */ 231 if (admin_data.set_nonstrict) 232 { 233 error (0, 0, "-U and -L are incompatible"); 234 goto usage_error; 235 } 236 admin_data.set_strict = 1; 237 break; 238 239 case 'U': 240 /* Probably could also complain if -U is specified multiple 241 times, although RCS doesn't and I suppose it is reasonable 242 just to have it mean the same as a single -U. */ 243 if (admin_data.set_strict) 244 { 245 error (0, 0, "-U and -L are incompatible"); 246 goto usage_error; 247 } 248 admin_data.set_nonstrict = 1; 249 break; 250 251 case 'n': 252 /* Mostly similar to cvs tag. Could also be parsing 253 the syntax of optarg, although for now we just pass 254 it to rcs as-is. Note that multiple -n options are 255 legal. */ 256 arg_add (&admin_data, 'n', optarg); 257 break; 258 259 case 'N': 260 /* Mostly similar to cvs tag. Could also be parsing 261 the syntax of optarg, although for now we just pass 262 it to rcs as-is. Note that multiple -N options are 263 legal. */ 264 arg_add (&admin_data, 'N', optarg); 265 break; 266 267 case 'm': 268 /* Change log message. Could also be parsing the syntax 269 of optarg, although for now we just pass it to rcs 270 as-is. Note that multiple -m options are legal. */ 271 arg_add (&admin_data, 'm', optarg); 272 break; 273 274 case 'o': 275 /* Delete revisions. Probably should also be parsing the 276 syntax of optarg, so that the client can give errors 277 rather than making the server take care of that. 278 Other than that I'm not sure whether it matters much 279 whether we parse it here or in admin_fileproc. 280 281 Note that multiple -o options are illegal, in RCS 282 as well as here. */ 283 284 if (admin_data.delete_revs != NULL) 285 { 286 error (0, 0, "duplicate '-o' option"); 287 goto usage_error; 288 } 289 admin_data.delete_revs = xmalloc (strlen (optarg) + 5); 290 strcpy (admin_data.delete_revs, "-o"); 291 strcat (admin_data.delete_revs, optarg); 292 break; 293 294 case 's': 295 /* Note that multiple -s options are legal. */ 296 arg_add (&admin_data, 's', optarg); 297 break; 298 299 case 't': 300 if (admin_data.desc != NULL) 301 { 302 error (0, 0, "duplicate 't' option"); 303 goto usage_error; 304 } 305 if (optarg == NULL) 306 admin_data.desc = xstrdup ("-t"); 307 else 308 { 309 admin_data.desc = xmalloc (strlen (optarg) + 5); 310 strcpy (admin_data.desc, "-t"); 311 strcat (admin_data.desc, optarg); 312 } 313 break; 314 315 case 'I': 316 /* At least in RCS this can be specified several times, 317 with the same meaning as being specified once. */ 318 admin_data.interactive = 1; 319 break; 320 321 case 'q': 322 admin_data.quiet = 1; 323 break; 324 325 case 'x': 326 error (0, 0, "the -x option has never done anything useful"); 327 error (0, 0, "RCS files in CVS always end in ,v"); 328 goto usage_error; 329 330 case 'V': 331 /* No longer supported. */ 332 error (0, 0, "the `-V' option is obsolete"); 333 break; 334 335 case 'k': 336 if (admin_data.kflag != NULL) 337 { 338 error (0, 0, "duplicate '-k' option"); 339 goto usage_error; 340 } 341 admin_data.kflag = RCS_check_kflag (optarg); 342 break; 343 default: 344 case '?': 345 /* getopt will have printed an error message. */ 346 347 usage_error: 348 /* Don't use command_name; it might be "server". */ 349 error (1, 0, "specify %s -H admin for usage information", 350 program_name); 351 } 352 } 353 argc -= optind; 354 argv += optind; 355 356 for (i = 0; i < admin_data.ac; ++i) 357 { 358 assert (admin_data.av[i][0] == '-'); 359 switch (admin_data.av[i][1]) 360 { 361 case 'm': 362 case 'l': 363 case 'u': 364 check_numeric (&admin_data.av[i][2], argc, argv); 365 break; 366 default: 367 break; 368 } 369 } 370 if (admin_data.branch != NULL) 371 check_numeric (admin_data.branch + 2, argc, argv); 372 if (admin_data.delete_revs != NULL) 373 { 374 char *p; 375 376 check_numeric (admin_data.delete_revs + 2, argc, argv); 377 p = strchr (admin_data.delete_revs + 2, ':'); 378 if (p != NULL && isdigit ((unsigned char) p[1])) 379 check_numeric (p + 1, argc, argv); 380 else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2])) 381 check_numeric (p + 2, argc, argv); 382 } 383 384 #ifdef CLIENT_SUPPORT 385 if (client_active) 386 { 387 /* We're the client side. Fire up the remote server. */ 388 start_server (); 389 390 ign_setup (); 391 392 /* Note that option_with_arg does not work for us, because some 393 of the options must be sent without a space between the option 394 and its argument. */ 395 if (admin_data.interactive) 396 error (1, 0, "-I option not useful with client/server"); 397 if (admin_data.branch != NULL) 398 send_arg (admin_data.branch); 399 if (admin_data.comment != NULL) 400 send_arg (admin_data.comment); 401 if (admin_data.set_strict) 402 send_arg ("-L"); 403 if (admin_data.set_nonstrict) 404 send_arg ("-U"); 405 if (admin_data.delete_revs != NULL) 406 send_arg (admin_data.delete_revs); 407 if (admin_data.desc != NULL) 408 send_arg (admin_data.desc); 409 if (admin_data.quiet) 410 send_arg ("-q"); 411 if (admin_data.kflag != NULL) 412 send_arg (admin_data.kflag); 413 414 for (i = 0; i < admin_data.ac; ++i) 415 send_arg (admin_data.av[i]); 416 417 send_files (argc, argv, 0, 0, SEND_NO_CONTENTS); 418 send_file_names (argc, argv, SEND_EXPAND_WILD); 419 send_to_server ("admin\012", 0); 420 err = get_responses_and_close (); 421 goto return_it; 422 } 423 #endif /* CLIENT_SUPPORT */ 424 425 lock_tree_for_write (argc, argv, 0, 0); 426 427 err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc, 428 (DIRLEAVEPROC) NULL, (void *)&admin_data, 429 argc, argv, 0, 430 W_LOCAL, 0, 0, (char *) NULL, 1); 431 Lock_Cleanup (); 432 433 return_it: 434 if (admin_data.branch != NULL) 435 free (admin_data.branch); 436 if (admin_data.comment != NULL) 437 free (admin_data.comment); 438 if (admin_data.delete_revs != NULL) 439 free (admin_data.delete_revs); 440 if (admin_data.kflag != NULL) 441 free (admin_data.kflag); 442 if (admin_data.desc != NULL) 443 free (admin_data.desc); 444 for (i = 0; i < admin_data.ac; ++i) 445 free (admin_data.av[i]); 446 if (admin_data.av != NULL) 447 free (admin_data.av); 448 449 return (err); 450 } 451 452 /* 453 * Called to run "rcs" on a particular file. 454 */ 455 /* ARGSUSED */ 456 static int 457 admin_fileproc (callerdat, finfo) 458 void *callerdat; 459 struct file_info *finfo; 460 { 461 struct admin_data *admin_data = (struct admin_data *) callerdat; 462 Vers_TS *vers; 463 char *version; 464 int i; 465 int status = 0; 466 RCSNode *rcs, *rcs2; 467 468 vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0); 469 470 version = vers->vn_user; 471 if (version == NULL) 472 goto exitfunc; 473 else if (strcmp (version, "0") == 0) 474 { 475 error (0, 0, "cannot admin newly added file `%s'", finfo->file); 476 goto exitfunc; 477 } 478 479 rcs = vers->srcfile; 480 if (rcs->flags & PARTIAL) 481 RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); 482 483 status = 0; 484 485 if (!admin_data->quiet) 486 { 487 cvs_output ("RCS file: ", 0); 488 cvs_output (rcs->path, 0); 489 cvs_output ("\n", 1); 490 } 491 492 if (admin_data->branch != NULL) 493 { 494 char *branch = &admin_data->branch[2]; 495 if (*branch != '\0' && ! isdigit ((unsigned char) *branch)) 496 { 497 branch = RCS_whatbranch (rcs, admin_data->branch + 2); 498 if (branch == NULL) 499 { 500 error (0, 0, "%s: Symbolic name %s is undefined.", 501 rcs->path, admin_data->branch + 2); 502 status = 1; 503 } 504 } 505 if (status == 0) 506 RCS_setbranch (rcs, branch); 507 if (branch != NULL && branch != &admin_data->branch[2]) 508 free (branch); 509 } 510 if (admin_data->comment != NULL) 511 { 512 if (rcs->comment != NULL) 513 free (rcs->comment); 514 rcs->comment = xstrdup (admin_data->comment + 2); 515 } 516 if (admin_data->set_strict) 517 rcs->strict_locks = 1; 518 if (admin_data->set_nonstrict) 519 rcs->strict_locks = 0; 520 if (admin_data->delete_revs != NULL) 521 { 522 char *s, *t, *rev1, *rev2; 523 /* Set for :, clear for ::. */ 524 int inclusive; 525 char *t2; 526 527 s = admin_data->delete_revs + 2; 528 inclusive = 1; 529 t = strchr (s, ':'); 530 if (t != NULL) 531 { 532 if (t[1] == ':') 533 { 534 inclusive = 0; 535 t2 = t + 2; 536 } 537 else 538 t2 = t + 1; 539 } 540 541 /* Note that we don't support '-' for ranges. RCS considers it 542 obsolete and it is problematic with tags containing '-'. "cvs log" 543 has made the same decision. */ 544 545 if (t == NULL) 546 { 547 /* -orev */ 548 rev1 = xstrdup (s); 549 rev2 = xstrdup (s); 550 } 551 else if (t == s) 552 { 553 /* -o:rev2 */ 554 rev1 = NULL; 555 rev2 = xstrdup (t2); 556 } 557 else 558 { 559 *t = '\0'; 560 rev1 = xstrdup (s); 561 *t = ':'; /* probably unnecessary */ 562 if (*t2 == '\0') 563 /* -orev1: */ 564 rev2 = NULL; 565 else 566 /* -orev1:rev2 */ 567 rev2 = xstrdup (t2); 568 } 569 570 if (rev1 == NULL && rev2 == NULL) 571 { 572 /* RCS segfaults if `-o:' is given */ 573 error (0, 0, "no valid revisions specified in `%s' option", 574 admin_data->delete_revs); 575 status = 1; 576 } 577 else 578 { 579 status |= RCS_delete_revs (rcs, rev1, rev2, inclusive); 580 if (rev1) 581 free (rev1); 582 if (rev2) 583 free (rev2); 584 } 585 } 586 if (admin_data->desc != NULL) 587 { 588 free (rcs->desc); 589 rcs->desc = NULL; 590 if (admin_data->desc[2] == '-') 591 rcs->desc = xstrdup (admin_data->desc + 3); 592 else 593 { 594 char *descfile = admin_data->desc + 2; 595 size_t bufsize = 0; 596 size_t len; 597 598 /* If -t specified with no argument, read from stdin. */ 599 if (*descfile == '\0') 600 descfile = NULL; 601 get_file (descfile, descfile, "r", &rcs->desc, &bufsize, &len); 602 } 603 } 604 if (admin_data->kflag != NULL) 605 { 606 char *kflag = admin_data->kflag + 2; 607 char *oldexpand = RCS_getexpand (rcs); 608 if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0) 609 RCS_setexpand (rcs, kflag); 610 } 611 612 /* Handle miscellaneous options. TODO: decide whether any or all 613 of these should have their own fields in the admin_data 614 structure. */ 615 for (i = 0; i < admin_data->ac; ++i) 616 { 617 char *arg; 618 char *p, *rev, *revnum, *tag, *msg; 619 char **users; 620 int argc, u; 621 Node *n; 622 RCSVers *delta; 623 624 arg = admin_data->av[i]; 625 switch (arg[1]) 626 { 627 case 'a': /* fall through */ 628 case 'e': 629 line2argv (&argc, &users, arg + 2, " ,\t\n"); 630 if (arg[1] == 'a') 631 for (u = 0; u < argc; ++u) 632 RCS_addaccess (rcs, users[u]); 633 else 634 for (u = 0; u < argc; ++u) 635 RCS_delaccess (rcs, users[u]); 636 free_names (&argc, users); 637 break; 638 case 'A': 639 640 /* See admin-19a-admin and friends in sanity.sh for 641 relative pathnames. It makes sense to think in 642 terms of a syntax which give pathnames relative to 643 the repository or repository corresponding to the 644 current directory or some such (and perhaps don't 645 include ,v), but trying to worry about such things 646 is a little pointless unless you first worry about 647 whether "cvs admin -A" as a whole makes any sense 648 (currently probably not, as access lists don't 649 affect the behavior of CVS). */ 650 651 rcs2 = RCS_parsercsfile (arg + 2); 652 if (rcs2 == NULL) 653 error (1, 0, "cannot continue"); 654 655 p = xstrdup (RCS_getaccess (rcs2)); 656 line2argv (&argc, &users, p, " \t\n"); 657 free (p); 658 freercsnode (&rcs2); 659 660 for (u = 0; u < argc; ++u) 661 RCS_addaccess (rcs, users[u]); 662 free_names (&argc, users); 663 break; 664 case 'n': /* fall through */ 665 case 'N': 666 if (arg[2] == '\0') 667 { 668 cvs_outerr ("missing symbolic name after ", 0); 669 cvs_outerr (arg, 0); 670 cvs_outerr ("\n", 1); 671 break; 672 } 673 p = strchr (arg, ':'); 674 if (p == NULL) 675 { 676 if (RCS_deltag (rcs, arg + 2) != 0) 677 { 678 error (0, 0, "%s: Symbolic name %s is undefined.", 679 rcs->path, 680 arg + 2); 681 status = 1; 682 continue; 683 } 684 break; 685 } 686 *p = '\0'; 687 tag = xstrdup (arg + 2); 688 *p++ = ':'; 689 690 /* Option `n' signals an error if this tag is already bound. */ 691 if (arg[1] == 'n') 692 { 693 n = findnode (RCS_symbols (rcs), tag); 694 if (n != NULL) 695 { 696 error (0, 0, 697 "%s: symbolic name %s already bound to %s", 698 rcs->path, 699 tag, n->data); 700 status = 1; 701 free (tag); 702 continue; 703 } 704 } 705 706 /* Attempt to perform the requested tagging. */ 707 708 if ((*p == 0 && (rev = RCS_head (rcs))) 709 || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */ 710 { 711 RCS_check_tag (tag); /* exit if not a valid tag */ 712 RCS_settag (rcs, tag, rev); 713 free (rev); 714 } 715 else 716 { 717 error (0, 0, 718 "%s: Symbolic name or revision %s is undefined", 719 rcs->path, p); 720 status = 1; 721 } 722 free (tag); 723 break; 724 case 's': 725 p = strchr (arg, ':'); 726 if (p == NULL) 727 { 728 tag = xstrdup (arg + 2); 729 rev = RCS_head (rcs); 730 } 731 else 732 { 733 *p = '\0'; 734 tag = xstrdup (arg + 2); 735 *p++ = ':'; 736 rev = xstrdup (p); 737 } 738 revnum = RCS_gettag (rcs, rev, 0, NULL); 739 free (rev); 740 if (revnum != NULL) 741 n = findnode (rcs->versions, revnum); 742 if (revnum == NULL || n == NULL) 743 { 744 error (0, 0, 745 "%s: can't set state of nonexisting revision %s", 746 rcs->path, 747 rev); 748 if (revnum != NULL) 749 free (revnum); 750 status = 1; 751 continue; 752 } 753 delta = (RCSVers *) n->data; 754 free (delta->state); 755 delta->state = tag; 756 break; 757 758 case 'm': 759 p = strchr (arg, ':'); 760 if (p == NULL) 761 { 762 error (0, 0, "%s: -m option lacks revision number", 763 rcs->path); 764 status = 1; 765 continue; 766 } 767 *p = '\0'; 768 rev = RCS_gettag (rcs, arg + 2, 0, NULL); 769 if (rev == NULL) 770 { 771 error (0, 0, "%s: no such revision %s", rcs->path, rev); 772 status = 1; 773 continue; 774 } 775 *p++ = ':'; 776 msg = p; 777 778 n = findnode (rcs->versions, rev); 779 delta = (RCSVers *) n->data; 780 if (delta->text == NULL) 781 { 782 delta->text = (Deltatext *) xmalloc (sizeof (Deltatext)); 783 memset ((void *) delta->text, 0, sizeof (Deltatext)); 784 } 785 delta->text->version = xstrdup (delta->version); 786 delta->text->log = make_message_rcslegal (msg); 787 break; 788 789 case 'l': 790 status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0); 791 break; 792 case 'u': 793 status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0); 794 break; 795 default: assert(0); /* can't happen */ 796 } 797 } 798 799 /* TODO: reconcile the weird discrepancies between 800 admin_data->quiet and quiet. */ 801 if (status == 0) 802 { 803 RCS_rewrite (rcs, NULL, NULL); 804 if (!admin_data->quiet) 805 cvs_output ("done\n", 5); 806 } 807 else 808 { 809 /* Note that this message should only occur after another 810 message has given a more specific error. The point of this 811 additional message is to make it clear that the previous problems 812 caused CVS to forget about the idea of modifying the RCS file. */ 813 error (0, 0, "cannot modify RCS file for `%s'", finfo->file); 814 815 /* Upon failure, we want to abandon any changes made to the 816 RCS data structure. Forcing a reparse does the trick, 817 but leaks memory and is kludgey. Should we export 818 free_rcsnode_contents for this purpose? */ 819 RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); 820 } 821 822 exitfunc: 823 freevers_ts (&vers); 824 return status; 825 } 826 827 /* 828 * Print a warm fuzzy message 829 */ 830 /* ARGSUSED */ 831 static Dtype 832 admin_dirproc (callerdat, dir, repos, update_dir, entries) 833 void *callerdat; 834 char *dir; 835 char *repos; 836 char *update_dir; 837 List *entries; 838 { 839 if (!quiet) 840 error (0, 0, "Administrating %s", update_dir); 841 return (R_PROCESS); 842 } 843