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