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