admin.c revision 128266
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, const char *dir, 19 const char *repos, const 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 cvs_cmd_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 on the 381 server. */ 382 if ( 383# ifdef CLIENT_SUPPORT 384 /* This is only "secure" on the server, since the user could edit the 385 * RCS file on a local host, but some people like this kind of 386 * check anyhow. The alternative would be to check only when 387 * (server_active) rather than when not on the client. 388 */ 389 !current_parsed_root->isremote && 390# endif /* CLIENT_SUPPORT */ 391 !only_k_option 392 && (grp = getgrnam(CVS_ADMIN_GROUP)) != NULL) 393 { 394#ifdef HAVE_GETGROUPS 395 gid_t *grps; 396 int n; 397 398 /* get number of auxiliary groups */ 399 n = getgroups (0, NULL); 400 if (n < 0) 401 error (1, errno, "unable to get number of auxiliary groups"); 402 grps = (gid_t *) xmalloc((n + 1) * sizeof *grps); 403 n = getgroups (n, grps); 404 if (n < 0) 405 error (1, errno, "unable to get list of auxiliary groups"); 406 grps[n] = getgid(); 407 for (i = 0; i <= n; i++) 408 if (grps[i] == grp->gr_gid) break; 409 free (grps); 410 if (i > n) 411 error (1, 0, "usage is restricted to members of the group %s", 412 CVS_ADMIN_GROUP); 413#else 414 char *me = getcaller(); 415 char **grnam; 416 417 for (grnam = grp->gr_mem; *grnam; grnam++) 418 if (strcmp (*grnam, me) == 0) break; 419 if (!*grnam && getgid() != grp->gr_gid) 420 error (1, 0, "usage is restricted to members of the group %s", 421 CVS_ADMIN_GROUP); 422#endif 423 } 424#endif /* defined CVS_ADMIN_GROUP */ 425 426 for (i = 0; i < admin_data.ac; ++i) 427 { 428 assert (admin_data.av[i][0] == '-'); 429 switch (admin_data.av[i][1]) 430 { 431 case 'm': 432 case 'l': 433 case 'u': 434 check_numeric (&admin_data.av[i][2], argc, argv); 435 break; 436 default: 437 break; 438 } 439 } 440 if (admin_data.branch != NULL) 441 check_numeric (admin_data.branch + 2, argc, argv); 442 if (admin_data.delete_revs != NULL) 443 { 444 char *p; 445 446 check_numeric (admin_data.delete_revs + 2, argc, argv); 447 p = strchr (admin_data.delete_revs + 2, ':'); 448 if (p != NULL && isdigit ((unsigned char) p[1])) 449 check_numeric (p + 1, argc, argv); 450 else if (p != NULL && p[1] == ':' && isdigit ((unsigned char) p[2])) 451 check_numeric (p + 2, argc, argv); 452 } 453 454#ifdef CLIENT_SUPPORT 455 if (current_parsed_root->isremote) 456 { 457 /* We're the client side. Fire up the remote server. */ 458 start_server (); 459 460 ign_setup (); 461 462 /* Note that option_with_arg does not work for us, because some 463 of the options must be sent without a space between the option 464 and its argument. */ 465 if (admin_data.interactive) 466 error (1, 0, "-I option not useful with client/server"); 467 if (admin_data.branch != NULL) 468 send_arg (admin_data.branch); 469 if (admin_data.comment != NULL) 470 send_arg (admin_data.comment); 471 if (admin_data.set_strict) 472 send_arg ("-L"); 473 if (admin_data.set_nonstrict) 474 send_arg ("-U"); 475 if (admin_data.delete_revs != NULL) 476 send_arg (admin_data.delete_revs); 477 if (admin_data.desc != NULL) 478 { 479 char *p = admin_data.desc; 480 send_to_server ("Argument -t-", 0); 481 while (*p) 482 { 483 if (*p == '\n') 484 { 485 send_to_server ("\012Argumentx ", 0); 486 ++p; 487 } 488 else 489 { 490 char *q = strchr (p, '\n'); 491 if (q == NULL) q = p + strlen (p); 492 send_to_server (p, q - p); 493 p = q; 494 } 495 } 496 send_to_server ("\012", 1); 497 } 498 /* Send this for all really_quiets since we know that it will be silently 499 * ignored when unneeded. This supports old servers. 500 */ 501 if (really_quiet) 502 send_arg ("-q"); 503 if (admin_data.kflag != NULL) 504 send_arg (admin_data.kflag); 505 506 for (i = 0; i < admin_data.ac; ++i) 507 send_arg (admin_data.av[i]); 508 509 send_arg ("--"); 510 send_files (argc, argv, 0, 0, SEND_NO_CONTENTS); 511 send_file_names (argc, argv, SEND_EXPAND_WILD); 512 send_to_server ("admin\012", 0); 513 err = get_responses_and_close (); 514 goto return_it; 515 } 516#endif /* CLIENT_SUPPORT */ 517 518 lock_tree_for_write (argc, argv, 0, W_LOCAL, 0); 519 520 err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc, 521 (DIRLEAVEPROC) NULL, (void *)&admin_data, 522 argc, argv, 0, 523 W_LOCAL, 0, CVS_LOCK_NONE, (char *) NULL, 1, 524 (char *) NULL); 525 Lock_Cleanup (); 526 527 return_it: 528 if (admin_data.branch != NULL) 529 free (admin_data.branch); 530 if (admin_data.comment != NULL) 531 free (admin_data.comment); 532 if (admin_data.delete_revs != NULL) 533 free (admin_data.delete_revs); 534 if (admin_data.kflag != NULL) 535 free (admin_data.kflag); 536 if (admin_data.desc != NULL) 537 free (admin_data.desc); 538 for (i = 0; i < admin_data.ac; ++i) 539 free (admin_data.av[i]); 540 if (admin_data.av != NULL) 541 free (admin_data.av); 542 543 return (err); 544} 545 546/* 547 * Called to run "rcs" on a particular file. 548 */ 549/* ARGSUSED */ 550static int 551admin_fileproc (callerdat, finfo) 552 void *callerdat; 553 struct file_info *finfo; 554{ 555 struct admin_data *admin_data = (struct admin_data *) callerdat; 556 Vers_TS *vers; 557 char *version; 558 int i; 559 int status = 0; 560 RCSNode *rcs, *rcs2; 561 562 vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0); 563 564 version = vers->vn_user; 565 if (version != NULL && strcmp (version, "0") == 0) 566 { 567 error (0, 0, "cannot admin newly added file `%s'", finfo->file); 568 status = 1; 569 goto exitfunc; 570 } 571 572 rcs = vers->srcfile; 573 if (rcs == NULL) 574 { 575 if (!really_quiet) 576 error (0, 0, "nothing known about %s", finfo->file); 577 status = 1; 578 goto exitfunc; 579 } 580 581 if (rcs->flags & PARTIAL) 582 RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); 583 584 if (!really_quiet) 585 { 586 cvs_output ("RCS file: ", 0); 587 cvs_output (rcs->path, 0); 588 cvs_output ("\n", 1); 589 } 590 591 if (admin_data->branch != NULL) 592 { 593 char *branch = &admin_data->branch[2]; 594 if (*branch != '\0' && ! isdigit ((unsigned char) *branch)) 595 { 596 branch = RCS_whatbranch (rcs, admin_data->branch + 2); 597 if (branch == NULL) 598 { 599 error (0, 0, "%s: Symbolic name %s is undefined.", 600 rcs->path, admin_data->branch + 2); 601 status = 1; 602 } 603 } 604 if (status == 0) 605 RCS_setbranch (rcs, branch); 606 if (branch != NULL && branch != &admin_data->branch[2]) 607 free (branch); 608 } 609 if (admin_data->comment != NULL) 610 { 611 if (rcs->comment != NULL) 612 free (rcs->comment); 613 rcs->comment = xstrdup (admin_data->comment + 2); 614 } 615 if (admin_data->set_strict) 616 rcs->strict_locks = 1; 617 if (admin_data->set_nonstrict) 618 rcs->strict_locks = 0; 619 if (admin_data->delete_revs != NULL) 620 { 621 char *s, *t, *rev1, *rev2; 622 /* Set for :, clear for ::. */ 623 int inclusive; 624 char *t2; 625 626 s = admin_data->delete_revs + 2; 627 inclusive = 1; 628 t = strchr (s, ':'); 629 if (t != NULL) 630 { 631 if (t[1] == ':') 632 { 633 inclusive = 0; 634 t2 = t + 2; 635 } 636 else 637 t2 = t + 1; 638 } 639 640 /* Note that we don't support '-' for ranges. RCS considers it 641 obsolete and it is problematic with tags containing '-'. "cvs log" 642 has made the same decision. */ 643 644 if (t == NULL) 645 { 646 /* -orev */ 647 rev1 = xstrdup (s); 648 rev2 = xstrdup (s); 649 } 650 else if (t == s) 651 { 652 /* -o:rev2 */ 653 rev1 = NULL; 654 rev2 = xstrdup (t2); 655 } 656 else 657 { 658 *t = '\0'; 659 rev1 = xstrdup (s); 660 *t = ':'; /* probably unnecessary */ 661 if (*t2 == '\0') 662 /* -orev1: */ 663 rev2 = NULL; 664 else 665 /* -orev1:rev2 */ 666 rev2 = xstrdup (t2); 667 } 668 669 if (rev1 == NULL && rev2 == NULL) 670 { 671 /* RCS segfaults if `-o:' is given */ 672 error (0, 0, "no valid revisions specified in `%s' option", 673 admin_data->delete_revs); 674 status = 1; 675 } 676 else 677 { 678 status |= RCS_delete_revs (rcs, rev1, rev2, inclusive); 679 if (rev1) 680 free (rev1); 681 if (rev2) 682 free (rev2); 683 } 684 } 685 if (admin_data->desc != NULL) 686 { 687 free (rcs->desc); 688 rcs->desc = xstrdup (admin_data->desc); 689 } 690 if (admin_data->kflag != NULL) 691 { 692 char *kflag = admin_data->kflag + 2; 693 char *oldexpand = RCS_getexpand (rcs); 694 if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0) 695 RCS_setexpand (rcs, kflag); 696 } 697 698 /* Handle miscellaneous options. TODO: decide whether any or all 699 of these should have their own fields in the admin_data 700 structure. */ 701 for (i = 0; i < admin_data->ac; ++i) 702 { 703 char *arg; 704 char *p, *rev, *revnum, *tag, *msg; 705 char **users; 706 int argc, u; 707 Node *n; 708 RCSVers *delta; 709 710 arg = admin_data->av[i]; 711 switch (arg[1]) 712 { 713 case 'a': /* fall through */ 714 case 'e': 715 line2argv (&argc, &users, arg + 2, " ,\t\n"); 716 if (arg[1] == 'a') 717 for (u = 0; u < argc; ++u) 718 RCS_addaccess (rcs, users[u]); 719 else if (argc == 0) 720 RCS_delaccess (rcs, NULL); 721 else 722 for (u = 0; u < argc; ++u) 723 RCS_delaccess (rcs, users[u]); 724 free_names (&argc, users); 725 break; 726 case 'A': 727 728 /* See admin-19a-admin and friends in sanity.sh for 729 relative pathnames. It makes sense to think in 730 terms of a syntax which give pathnames relative to 731 the repository or repository corresponding to the 732 current directory or some such (and perhaps don't 733 include ,v), but trying to worry about such things 734 is a little pointless unless you first worry about 735 whether "cvs admin -A" as a whole makes any sense 736 (currently probably not, as access lists don't 737 affect the behavior of CVS). */ 738 739 rcs2 = RCS_parsercsfile (arg + 2); 740 if (rcs2 == NULL) 741 error (1, 0, "cannot continue"); 742 743 p = xstrdup (RCS_getaccess (rcs2)); 744 line2argv (&argc, &users, p, " \t\n"); 745 free (p); 746 freercsnode (&rcs2); 747 748 for (u = 0; u < argc; ++u) 749 RCS_addaccess (rcs, users[u]); 750 free_names (&argc, users); 751 break; 752 case 'n': /* fall through */ 753 case 'N': 754 if (arg[2] == '\0') 755 { 756 cvs_outerr ("missing symbolic name after ", 0); 757 cvs_outerr (arg, 0); 758 cvs_outerr ("\n", 1); 759 break; 760 } 761 p = strchr (arg, ':'); 762 if (p == NULL) 763 { 764 if (RCS_deltag (rcs, arg + 2) != 0) 765 { 766 error (0, 0, "%s: Symbolic name %s is undefined.", 767 rcs->path, 768 arg + 2); 769 status = 1; 770 continue; 771 } 772 break; 773 } 774 *p = '\0'; 775 tag = xstrdup (arg + 2); 776 *p++ = ':'; 777 778 /* Option `n' signals an error if this tag is already bound. */ 779 if (arg[1] == 'n') 780 { 781 n = findnode (RCS_symbols (rcs), tag); 782 if (n != NULL) 783 { 784 error (0, 0, 785 "%s: symbolic name %s already bound to %s", 786 rcs->path, 787 tag, (char *)n->data); 788 status = 1; 789 free (tag); 790 continue; 791 } 792 } 793 794 /* Attempt to perform the requested tagging. */ 795 796 if ((*p == 0 && (rev = RCS_head (rcs))) 797 || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */ 798 { 799 RCS_check_tag (tag); /* exit if not a valid tag */ 800 RCS_settag (rcs, tag, rev); 801 free (rev); 802 } 803 else 804 { 805 if (!really_quiet) 806 error (0, 0, 807 "%s: Symbolic name or revision %s is undefined.", 808 rcs->path, p); 809 status = 1; 810 } 811 free (tag); 812 break; 813 case 's': 814 p = strchr (arg, ':'); 815 if (p == NULL) 816 { 817 tag = xstrdup (arg + 2); 818 rev = RCS_head (rcs); 819 } 820 else 821 { 822 *p = '\0'; 823 tag = xstrdup (arg + 2); 824 *p++ = ':'; 825 rev = xstrdup (p); 826 } 827 revnum = RCS_gettag (rcs, rev, 0, NULL); 828 if (revnum != NULL) 829 { 830 n = findnode (rcs->versions, revnum); 831 free (revnum); 832 } 833 else 834 n = NULL; 835 if (n == NULL) 836 { 837 error (0, 0, 838 "%s: can't set state of nonexisting revision %s", 839 rcs->path, 840 rev); 841 free (rev); 842 status = 1; 843 continue; 844 } 845 free (rev); 846 delta = n->data; 847 free (delta->state); 848 delta->state = tag; 849 break; 850 851 case 'm': 852 p = strchr (arg, ':'); 853 if (p == NULL) 854 { 855 error (0, 0, "%s: -m option lacks revision number", 856 rcs->path); 857 status = 1; 858 continue; 859 } 860 *p = '\0'; /* temporarily make arg+2 its own string */ 861 rev = RCS_gettag (rcs, arg + 2, 1, NULL); /* Force tag match */ 862 if (rev == NULL) 863 { 864 error (0, 0, "%s: no such revision %s", rcs->path, arg+2); 865 status = 1; 866 *p = ':'; /* restore the full text of the -m argument */ 867 continue; 868 } 869 msg = p+1; 870 871 n = findnode (rcs->versions, rev); 872 /* tags may exist against non-existing versions */ 873 if (n == NULL) 874 { 875 error (0, 0, "%s: no such revision %s: %s", 876 rcs->path, arg+2, rev); 877 status = 1; 878 *p = ':'; /* restore the full text of the -m argument */ 879 free (rev); 880 continue; 881 } 882 *p = ':'; /* restore the full text of the -m argument */ 883 free (rev); 884 885 delta = n->data; 886 if (delta->text == NULL) 887 { 888 delta->text = (Deltatext *) xmalloc (sizeof (Deltatext)); 889 memset ((void *) delta->text, 0, sizeof (Deltatext)); 890 } 891 delta->text->version = xstrdup (delta->version); 892 delta->text->log = make_message_rcslegal (msg); 893 break; 894 895 case 'l': 896 status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0); 897 break; 898 case 'u': 899 status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0); 900 break; 901 default: assert(0); /* can't happen */ 902 } 903 } 904 905 if (status == 0) 906 { 907 RCS_rewrite (rcs, NULL, NULL); 908 if (!really_quiet) 909 cvs_output ("done\n", 5); 910 } 911 else 912 { 913 /* Note that this message should only occur after another 914 message has given a more specific error. The point of this 915 additional message is to make it clear that the previous problems 916 caused CVS to forget about the idea of modifying the RCS file. */ 917 if (!really_quiet) 918 error (0, 0, "RCS file for `%s' not modified.", finfo->file); 919 RCS_abandon (rcs); 920 } 921 922 exitfunc: 923 freevers_ts (&vers); 924 return status; 925} 926 927/* 928 * Print a warm fuzzy message 929 */ 930/* ARGSUSED */ 931static Dtype 932admin_dirproc (callerdat, dir, repos, update_dir, entries) 933 void *callerdat; 934 const char *dir; 935 const char *repos; 936 const char *update_dir; 937 List *entries; 938{ 939 if (!quiet) 940 error (0, 0, "Administrating %s", update_dir); 941 return (R_PROCESS); 942} 943