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, 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. */ 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 /* 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). */ 114 static void 115 arg_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 142 int 143 admin (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') 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_files (argc, argv, 0, 0, SEND_NO_CONTENTS); 500 send_file_names (argc, argv, SEND_EXPAND_WILD); 501 send_to_server ("admin\012", 0); 502 err = get_responses_and_close (); 503 goto return_it; 504 } 505 #endif /* CLIENT_SUPPORT */ 506 507 lock_tree_for_write (argc, argv, 0, W_LOCAL, 0); 508 509 err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc, 510 (DIRLEAVEPROC) NULL, (void *)&admin_data, 511 argc, argv, 0, 512 W_LOCAL, 0, 0, (char *) NULL, 1); 513 Lock_Cleanup (); 514 515 return_it: 516 if (admin_data.branch != NULL) 517 free (admin_data.branch); 518 if (admin_data.comment != NULL) 519 free (admin_data.comment); 520 if (admin_data.delete_revs != NULL) 521 free (admin_data.delete_revs); 522 if (admin_data.kflag != NULL) 523 free (admin_data.kflag); 524 if (admin_data.desc != NULL) 525 free (admin_data.desc); 526 for (i = 0; i < admin_data.ac; ++i) 527 free (admin_data.av[i]); 528 if (admin_data.av != NULL) 529 free (admin_data.av); 530 531 return (err); 532 } 533 534 /* 535 * Called to run "rcs" on a particular file. 536 */ 537 /* ARGSUSED */ 538 static int 539 admin_fileproc (callerdat, finfo) 540 void *callerdat; 541 struct file_info *finfo; 542 { 543 struct admin_data *admin_data = (struct admin_data *) callerdat; 544 Vers_TS *vers; 545 char *version; 546 int i; 547 int status = 0; 548 RCSNode *rcs, *rcs2; 549 550 vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0); 551 552 version = vers->vn_user; 553 if (version == NULL) 554 goto exitfunc; 555 else if (strcmp (version, "0") == 0) 556 { 557 error (0, 0, "cannot admin newly added file `%s'", finfo->file); 558 goto exitfunc; 559 } 560 561 rcs = vers->srcfile; 562 if (rcs == NULL) 563 { 564 error (0, 0, "lost revision control file for `%s'", finfo->file); 565 goto exitfunc; 566 } 567 if (rcs->flags & PARTIAL) 568 RCS_reparsercsfile (rcs, (FILE **) NULL, (struct rcsbuffer *) NULL); 569 570 status = 0; 571 572 if (!really_quiet) 573 { 574 cvs_output ("RCS file: ", 0); 575 cvs_output (rcs->path, 0); 576 cvs_output ("\n", 1); 577 } 578 579 if (admin_data->branch != NULL) 580 { 581 char *branch = &admin_data->branch[2]; 582 if (*branch != '\0' && ! isdigit ((unsigned char) *branch)) 583 { 584 branch = RCS_whatbranch (rcs, admin_data->branch + 2); 585 if (branch == NULL) 586 { 587 error (0, 0, "%s: Symbolic name %s is undefined.", 588 rcs->path, admin_data->branch + 2); 589 status = 1; 590 } 591 } 592 if (status == 0) 593 RCS_setbranch (rcs, branch); 594 if (branch != NULL && branch != &admin_data->branch[2]) 595 free (branch); 596 } 597 if (admin_data->comment != NULL) 598 { 599 if (rcs->comment != NULL) 600 free (rcs->comment); 601 rcs->comment = xstrdup (admin_data->comment + 2); 602 } 603 if (admin_data->set_strict) 604 rcs->strict_locks = 1; 605 if (admin_data->set_nonstrict) 606 rcs->strict_locks = 0; 607 if (admin_data->delete_revs != NULL) 608 { 609 char *s, *t, *rev1, *rev2; 610 /* Set for :, clear for ::. */ 611 int inclusive; 612 char *t2; 613 614 s = admin_data->delete_revs + 2; 615 inclusive = 1; 616 t = strchr (s, ':'); 617 if (t != NULL) 618 { 619 if (t[1] == ':') 620 { 621 inclusive = 0; 622 t2 = t + 2; 623 } 624 else 625 t2 = t + 1; 626 } 627 628 /* Note that we don't support '-' for ranges. RCS considers it 629 obsolete and it is problematic with tags containing '-'. "cvs log" 630 has made the same decision. */ 631 632 if (t == NULL) 633 { 634 /* -orev */ 635 rev1 = xstrdup (s); 636 rev2 = xstrdup (s); 637 } 638 else if (t == s) 639 { 640 /* -o:rev2 */ 641 rev1 = NULL; 642 rev2 = xstrdup (t2); 643 } 644 else 645 { 646 *t = '\0'; 647 rev1 = xstrdup (s); 648 *t = ':'; /* probably unnecessary */ 649 if (*t2 == '\0') 650 /* -orev1: */ 651 rev2 = NULL; 652 else 653 /* -orev1:rev2 */ 654 rev2 = xstrdup (t2); 655 } 656 657 if (rev1 == NULL && rev2 == NULL) 658 { 659 /* RCS segfaults if `-o:' is given */ 660 error (0, 0, "no valid revisions specified in `%s' option", 661 admin_data->delete_revs); 662 status = 1; 663 } 664 else 665 { 666 status |= RCS_delete_revs (rcs, rev1, rev2, inclusive); 667 if (rev1) 668 free (rev1); 669 if (rev2) 670 free (rev2); 671 } 672 } 673 if (admin_data->desc != NULL) 674 { 675 free (rcs->desc); 676 rcs->desc = xstrdup (admin_data->desc); 677 } 678 if (admin_data->kflag != NULL) 679 { 680 char *kflag = admin_data->kflag + 2; 681 char *oldexpand = RCS_getexpand (rcs); 682 if (oldexpand == NULL || strcmp (oldexpand, kflag) != 0) 683 RCS_setexpand (rcs, kflag); 684 } 685 686 /* Handle miscellaneous options. TODO: decide whether any or all 687 of these should have their own fields in the admin_data 688 structure. */ 689 for (i = 0; i < admin_data->ac; ++i) 690 { 691 char *arg; 692 char *p, *rev, *revnum, *tag, *msg; 693 char **users; 694 int argc, u; 695 Node *n; 696 RCSVers *delta; 697 698 arg = admin_data->av[i]; 699 switch (arg[1]) 700 { 701 case 'a': /* fall through */ 702 case 'e': 703 line2argv (&argc, &users, arg + 2, " ,\t\n"); 704 if (arg[1] == 'a') 705 for (u = 0; u < argc; ++u) 706 RCS_addaccess (rcs, users[u]); 707 else if (argc == 0) 708 RCS_delaccess (rcs, NULL); 709 else 710 for (u = 0; u < argc; ++u) 711 RCS_delaccess (rcs, users[u]); 712 free_names (&argc, users); 713 break; 714 case 'A': 715 716 /* See admin-19a-admin and friends in sanity.sh for 717 relative pathnames. It makes sense to think in 718 terms of a syntax which give pathnames relative to 719 the repository or repository corresponding to the 720 current directory or some such (and perhaps don't 721 include ,v), but trying to worry about such things 722 is a little pointless unless you first worry about 723 whether "cvs admin -A" as a whole makes any sense 724 (currently probably not, as access lists don't 725 affect the behavior of CVS). */ 726 727 rcs2 = RCS_parsercsfile (arg + 2); 728 if (rcs2 == NULL) 729 error (1, 0, "cannot continue"); 730 731 p = xstrdup (RCS_getaccess (rcs2)); 732 line2argv (&argc, &users, p, " \t\n"); 733 free (p); 734 freercsnode (&rcs2); 735 736 for (u = 0; u < argc; ++u) 737 RCS_addaccess (rcs, users[u]); 738 free_names (&argc, users); 739 break; 740 case 'n': /* fall through */ 741 case 'N': 742 if (arg[2] == '\0') 743 { 744 cvs_outerr ("missing symbolic name after ", 0); 745 cvs_outerr (arg, 0); 746 cvs_outerr ("\n", 1); 747 break; 748 } 749 p = strchr (arg, ':'); 750 if (p == NULL) 751 { 752 if (RCS_deltag (rcs, arg + 2) != 0) 753 { 754 error (0, 0, "%s: Symbolic name %s is undefined.", 755 rcs->path, 756 arg + 2); 757 status = 1; 758 continue; 759 } 760 break; 761 } 762 *p = '\0'; 763 tag = xstrdup (arg + 2); 764 *p++ = ':'; 765 766 /* Option `n' signals an error if this tag is already bound. */ 767 if (arg[1] == 'n') 768 { 769 n = findnode (RCS_symbols (rcs), tag); 770 if (n != NULL) 771 { 772 error (0, 0, 773 "%s: symbolic name %s already bound to %s", 774 rcs->path, 775 tag, n->data); 776 status = 1; 777 free (tag); 778 continue; 779 } 780 } 781 782 /* Attempt to perform the requested tagging. */ 783 784 if ((*p == 0 && (rev = RCS_head (rcs))) 785 || (rev = RCS_tag2rev (rcs, p))) /* tag2rev may exit */ 786 { 787 RCS_check_tag (tag); /* exit if not a valid tag */ 788 RCS_settag (rcs, tag, rev); 789 free (rev); 790 } 791 else 792 { 793 if (!really_quiet) 794 error (0, 0, 795 "%s: Symbolic name or revision %s is undefined.", 796 rcs->path, p); 797 status = 1; 798 } 799 free (tag); 800 break; 801 case 's': 802 p = strchr (arg, ':'); 803 if (p == NULL) 804 { 805 tag = xstrdup (arg + 2); 806 rev = RCS_head (rcs); 807 } 808 else 809 { 810 *p = '\0'; 811 tag = xstrdup (arg + 2); 812 *p++ = ':'; 813 rev = xstrdup (p); 814 } 815 revnum = RCS_gettag (rcs, rev, 0, NULL); 816 if (revnum != NULL) 817 { 818 n = findnode (rcs->versions, revnum); 819 free (revnum); 820 } 821 else 822 n = NULL; 823 if (n == NULL) 824 { 825 error (0, 0, 826 "%s: can't set state of nonexisting revision %s", 827 rcs->path, 828 rev); 829 free (rev); 830 status = 1; 831 continue; 832 } 833 free (rev); 834 delta = (RCSVers *) n->data; 835 free (delta->state); 836 delta->state = tag; 837 break; 838 839 case 'm': 840 p = strchr (arg, ':'); 841 if (p == NULL) 842 { 843 error (0, 0, "%s: -m option lacks revision number", 844 rcs->path); 845 status = 1; 846 continue; 847 } 848 *p = '\0'; 849 rev = RCS_gettag (rcs, arg + 2, 0, NULL); 850 if (rev == NULL) 851 { 852 error (0, 0, "%s: no such revision %s", rcs->path, rev); 853 status = 1; 854 continue; 855 } 856 *p++ = ':'; 857 msg = p; 858 859 n = findnode (rcs->versions, rev); 860 free (rev); 861 delta = (RCSVers *) n->data; 862 if (delta->text == NULL) 863 { 864 delta->text = (Deltatext *) xmalloc (sizeof (Deltatext)); 865 memset ((void *) delta->text, 0, sizeof (Deltatext)); 866 } 867 delta->text->version = xstrdup (delta->version); 868 delta->text->log = make_message_rcslegal (msg); 869 break; 870 871 case 'l': 872 status |= RCS_lock (rcs, arg[2] ? arg + 2 : NULL, 0); 873 break; 874 case 'u': 875 status |= RCS_unlock (rcs, arg[2] ? arg + 2 : NULL, 0); 876 break; 877 default: assert(0); /* can't happen */ 878 } 879 } 880 881 if (status == 0) 882 { 883 RCS_rewrite (rcs, NULL, NULL); 884 if (!really_quiet) 885 cvs_output ("done\n", 5); 886 } 887 else 888 { 889 /* Note that this message should only occur after another 890 message has given a more specific error. The point of this 891 additional message is to make it clear that the previous problems 892 caused CVS to forget about the idea of modifying the RCS file. */ 893 if (!really_quiet) 894 error (0, 0, "RCS file for `%s' not modified.", finfo->file); 895 RCS_abandon (rcs); 896 } 897 898 exitfunc: 899 freevers_ts (&vers); 900 return status; 901 } 902 903 /* 904 * Print a warm fuzzy message 905 */ 906 /* ARGSUSED */ 907 static Dtype 908 admin_dirproc (callerdat, dir, repos, update_dir, entries) 909 void *callerdat; 910 char *dir; 911 char *repos; 912 char *update_dir; 913 List *entries; 914 { 915 if (!quiet) 916 error (0, 0, "Administrating %s", update_dir); 917 return (R_PROCESS); 918 } 919