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