1/* Change RCS file attributes. */ 2 3/* Copyright 1982, 1988, 1989 Walter Tichy 4 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert 5 Distributed under license by the Free Software Foundation, Inc. 6 7This file is part of RCS. 8 9RCS is free software; you can redistribute it and/or modify 10it under the terms of the GNU General Public License as published by 11the Free Software Foundation; either version 2, or (at your option) 12any later version. 13 14RCS is distributed in the hope that it will be useful, 15but WITHOUT ANY WARRANTY; without even the implied warranty of 16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17GNU General Public License for more details. 18 19You should have received a copy of the GNU General Public License 20along with RCS; see the file COPYING. 21If not, write to the Free Software Foundation, 2259 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 23 24Report problems and direct all questions to: 25 26 rcs-bugs@cs.purdue.edu 27 28*/ 29 30/* 31 * Revision 5.21 1995/06/16 06:19:24 eggert 32 * Update FSF address. 33 * 34 * Revision 5.20 1995/06/01 16:23:43 eggert 35 * (main): Warn if no options were given. Punctuate messages properly. 36 * 37 * (sendmail): Rewind mailmess before flushing it. 38 * Output another warning if mail should work but fails. 39 * 40 * (buildeltatext): Pass "--binary" if -kb and if --binary makes a difference. 41 * 42 * Revision 5.19 1994/03/17 14:05:48 eggert 43 * Use ORCSerror to clean up after a fatal error. Remove lint. 44 * Specify subprocess input via file descriptor, not file name. Remove lint. 45 * Flush stderr after prompt. 46 * 47 * Revision 5.18 1993/11/09 17:40:15 eggert 48 * -V now prints version on stdout and exits. Don't print usage twice. 49 * 50 * Revision 5.17 1993/11/03 17:42:27 eggert 51 * Add -z. Don't lose track of -m or -t when there are no other changes. 52 * Don't discard ignored phrases. Improve quality of diagnostics. 53 * 54 * Revision 5.16 1992/07/28 16:12:44 eggert 55 * rcs -l now asks whether you want to break the lock. 56 * Add -V. Set RCS file's mode and time at right moment. 57 * 58 * Revision 5.15 1992/02/17 23:02:20 eggert 59 * Add -T. 60 * 61 * Revision 5.14 1992/01/27 16:42:53 eggert 62 * Add -M. Avoid invoking umask(); it's one less thing to configure. 63 * Add support for bad_creat0. lint -> RCS_lint 64 * 65 * Revision 5.13 1992/01/06 02:42:34 eggert 66 * Avoid changing RCS file in common cases where no change can occur. 67 * 68 * Revision 5.12 1991/11/20 17:58:08 eggert 69 * Don't read the delta tree from a nonexistent RCS file. 70 * 71 * Revision 5.11 1991/10/07 17:32:46 eggert 72 * Remove lint. 73 * 74 * Revision 5.10 1991/08/19 23:17:54 eggert 75 * Add -m, -r$, piece tables. Revision separator is `:', not `-'. Tune. 76 * 77 * Revision 5.9 1991/04/21 11:58:18 eggert 78 * Add -x, RCSINIT, MS-DOS support. 79 * 80 * Revision 5.8 1991/02/25 07:12:38 eggert 81 * strsave -> str_save (DG/UX name clash) 82 * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability 83 * 84 * Revision 5.7 1990/12/18 17:19:21 eggert 85 * Fix bug with multiple -n and -N options. 86 * 87 * Revision 5.6 1990/12/04 05:18:40 eggert 88 * Use -I for prompts and -q for diagnostics. 89 * 90 * Revision 5.5 1990/11/11 00:06:35 eggert 91 * Fix `rcs -e' core dump. 92 * 93 * Revision 5.4 1990/11/01 05:03:33 eggert 94 * Add -I and new -t behavior. Permit arbitrary data in logs. 95 * 96 * Revision 5.3 1990/10/04 06:30:16 eggert 97 * Accumulate exit status across files. 98 * 99 * Revision 5.2 1990/09/04 08:02:17 eggert 100 * Standardize yes-or-no procedure. 101 * 102 * Revision 5.1 1990/08/29 07:13:51 eggert 103 * Remove unused setuid support. Clean old log messages too. 104 * 105 * Revision 5.0 1990/08/22 08:12:42 eggert 106 * Don't lose names when applying -a option to multiple files. 107 * Remove compile-time limits; use malloc instead. Add setuid support. 108 * Permit dates past 1999/12/31. Make lock and temp files faster and safer. 109 * Ansify and Posixate. Add -V. Fix umask bug. Make linting easier. Tune. 110 * Yield proper exit status. Check diff's output. 111 * 112 * Revision 4.11 89/05/01 15:12:06 narten 113 * changed copyright header to reflect current distribution rules 114 * 115 * Revision 4.10 88/11/08 16:01:54 narten 116 * didn't install previous patch correctly 117 * 118 * Revision 4.9 88/11/08 13:56:01 narten 119 * removed include <sysexits.h> (not needed) 120 * minor fix for -A option 121 * 122 * Revision 4.8 88/08/09 19:12:27 eggert 123 * Don't access freed storage. 124 * Use execv(), not system(); yield proper exit status; remove lint. 125 * 126 * Revision 4.7 87/12/18 11:37:17 narten 127 * lint cleanups (Guy Harris) 128 * 129 * Revision 4.6 87/10/18 10:28:48 narten 130 * Updating verison numbers. Changes relative to 1.1 are actually 131 * relative to 4.3 132 * 133 * Revision 1.4 87/09/24 13:58:52 narten 134 * Sources now pass through lint (if you ignore printf/sprintf/fprintf 135 * warnings) 136 * 137 * Revision 1.3 87/03/27 14:21:55 jenkins 138 * Port to suns 139 * 140 * Revision 1.2 85/12/17 13:59:09 albitz 141 * Changed setstate to rcs_setstate because of conflict with random.o. 142 * 143 * Revision 4.3 83/12/15 12:27:33 wft 144 * rcs -u now breaks most recent lock if it can't find a lock by the caller. 145 * 146 * Revision 4.2 83/12/05 10:18:20 wft 147 * Added conditional compilation for sending mail. 148 * Alternatives: V4_2BSD, V6, USG, and other. 149 * 150 * Revision 4.1 83/05/10 16:43:02 wft 151 * Simplified breaklock(); added calls to findlock() and getcaller(). 152 * Added option -b (default branch). Updated -s and -w for -b. 153 * Removed calls to stat(); now done by pairfilenames(). 154 * Replaced most catchints() calls with restoreints(). 155 * Removed check for exit status of delivermail(). 156 * Directed all interactive output to stderr. 157 * 158 * Revision 3.9.1.1 83/12/02 22:08:51 wft 159 * Added conditional compilation for 4.2 sendmail and 4.1 delivermail. 160 * 161 * Revision 3.9 83/02/15 15:38:39 wft 162 * Added call to fastcopy() to copy remainder of RCS file. 163 * 164 * Revision 3.8 83/01/18 17:37:51 wft 165 * Changed sendmail(): now uses delivermail, and asks whether to break the lock. 166 * 167 * Revision 3.7 83/01/15 18:04:25 wft 168 * Removed putree(); replaced with puttree() in rcssyn.c. 169 * Combined putdellog() and scanlogtext(); deleted putdellog(). 170 * Cleaned up diagnostics and error messages. Fixed problem with 171 * mutilated files in case of deletions in 2 files in a single command. 172 * Changed marking of selector from 'D' to DELETE. 173 * 174 * Revision 3.6 83/01/14 15:37:31 wft 175 * Added ignoring of interrupts while new RCS file is renamed; 176 * Avoids deletion of RCS files by interrupts. 177 * 178 * Revision 3.5 82/12/10 21:11:39 wft 179 * Removed unused variables, fixed checking of return code from diff, 180 * introduced variant COMPAT2 for skipping Suffix on -A files. 181 * 182 * Revision 3.4 82/12/04 13:18:20 wft 183 * Replaced getdelta() with gettree(), changed breaklock to update 184 * field lockedby, added some diagnostics. 185 * 186 * Revision 3.3 82/12/03 17:08:04 wft 187 * Replaced getlogin() with getpwuid(), flcose() with ffclose(), 188 * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x). 189 * fixed -u for missing revno. Disambiguated structure members. 190 * 191 * Revision 3.2 82/10/18 21:05:07 wft 192 * rcs -i now generates a file mode given by the umask minus write permission; 193 * otherwise, rcs keeps the mode, but removes write permission. 194 * I added a check for write error, fixed call to getlogin(), replaced 195 * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed 196 * conflicting, long identifiers. 197 * 198 * Revision 3.1 82/10/13 16:11:07 wft 199 * fixed type of variables receiving from getc() (char -> int). 200 */ 201 202 203#include "rcsbase.h" 204 205struct Lockrev { 206 char const *revno; 207 struct Lockrev * nextrev; 208}; 209 210struct Symrev { 211 char const *revno; 212 char const *ssymbol; 213 int override; 214 struct Symrev * nextsym; 215}; 216 217struct Message { 218 char const *revno; 219 struct cbuf message; 220 struct Message *nextmessage; 221}; 222 223struct Status { 224 char const *revno; 225 char const *status; 226 struct Status * nextstatus; 227}; 228 229enum changeaccess {append, erase}; 230struct chaccess { 231 char const *login; 232 enum changeaccess command; 233 struct chaccess *nextchaccess; 234}; 235 236struct delrevpair { 237 char const *strt; 238 char const *end; 239 int code; 240}; 241 242static int branchpoint P((struct hshentry*,struct hshentry*)); 243static int breaklock P((struct hshentry const*)); 244static int buildeltatext P((struct hshentries const*)); 245static int doaccess P((void)); 246static int doassoc P((void)); 247static int dolocks P((void)); 248static int domessages P((void)); 249static int rcs_setstate P((char const*,char const*)); 250static int removerevs P((void)); 251static int sendmail P((char const*,char const*)); 252static int setlock P((char const*)); 253static struct Lockrev **rmnewlocklst P((char const*)); 254static struct hshentry *searchcutpt P((char const*,int,struct hshentries*)); 255static void buildtree P((void)); 256static void cleanup P((void)); 257static void getaccessor P((char*,enum changeaccess)); 258static void getassoclst P((int,char*)); 259static void getchaccess P((char const*,enum changeaccess)); 260static void getdelrev P((char*)); 261static void getmessage P((char*)); 262static void getstates P((char*)); 263static void scanlogtext P((struct hshentry*,int)); 264 265static struct buf numrev; 266static char const *headstate; 267static int chgheadstate, exitstatus, lockhead, unlockcaller; 268static int suppress_mail; 269static struct Lockrev *newlocklst, *rmvlocklst; 270static struct Message *messagelst, **nextmessage; 271static struct Status *statelst, **nextstate; 272static struct Symrev *assoclst, **nextassoc; 273static struct chaccess *chaccess, **nextchaccess; 274static struct delrevpair delrev; 275static struct hshentry *cuthead, *cuttail, *delstrt; 276static struct hshentries *gendeltas; 277 278mainProg(rcsId, "rcs", "$FreeBSD$") 279{ 280 static char const cmdusage[] = 281 "\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iILqTU} -ksubst -mrev:msg -{nN}name[:[rev]] -orange -sstate[:rev] -t[text] -Vn -xsuff -zzone file ..."; 282 283 char *a, **newargv, *textfile; 284 char const *branchsym, *commsyml; 285 int branchflag, changed, expmode, initflag; 286 int strictlock, strict_selected, textflag; 287 int keepRCStime, Ttimeflag; 288 size_t commsymlen; 289 struct buf branchnum; 290 struct Lockrev *lockpt; 291 struct Lockrev **curlock, **rmvlock; 292 struct Status * curstate; 293 294 nosetid(); 295 296 nextassoc = &assoclst; 297 nextchaccess = &chaccess; 298 nextmessage = &messagelst; 299 nextstate = &statelst; 300 branchsym = commsyml = textfile = 0; 301 branchflag = strictlock = false; 302 bufautobegin(&branchnum); 303 commsymlen = 0; 304 curlock = &newlocklst; 305 rmvlock = &rmvlocklst; 306 expmode = -1; 307 suffixes = X_DEFAULT; 308 initflag= textflag = false; 309 strict_selected = 0; 310 Ttimeflag = false; 311 312 /* preprocessing command options */ 313 if (1 < argc && argv[1][0] != '-') 314 warn("No options were given; this usage is obsolescent."); 315 316 argc = getRCSINIT(argc, argv, &newargv); 317 argv = newargv; 318 while (a = *++argv, 0<--argc && *a++=='-') { 319 switch (*a++) { 320 321 case 'i': /* initial version */ 322 initflag = true; 323 break; 324 325 case 'b': /* change default branch */ 326 if (branchflag) redefined('b'); 327 branchflag= true; 328 branchsym = a; 329 break; 330 331 case 'c': /* change comment symbol */ 332 if (commsyml) redefined('c'); 333 commsyml = a; 334 commsymlen = strlen(a); 335 break; 336 337 case 'a': /* add new accessor */ 338 getaccessor(*argv+1, append); 339 break; 340 341 case 'A': /* append access list according to accessfile */ 342 if (!*a) { 343 error("missing pathname after -A"); 344 break; 345 } 346 *argv = a; 347 if (0 < pairnames(1,argv,rcsreadopen,true,false)) { 348 while (AccessList) { 349 getchaccess(str_save(AccessList->login),append); 350 AccessList = AccessList->nextaccess; 351 } 352 Izclose(&finptr); 353 } 354 break; 355 356 case 'e': /* remove accessors */ 357 getaccessor(*argv+1, erase); 358 break; 359 360 case 'l': /* lock a revision if it is unlocked */ 361 if (!*a) { 362 /* Lock head or default branch. */ 363 lockhead = true; 364 break; 365 } 366 *curlock = lockpt = talloc(struct Lockrev); 367 lockpt->revno = a; 368 lockpt->nextrev = 0; 369 curlock = &lockpt->nextrev; 370 break; 371 372 case 'u': /* release lock of a locked revision */ 373 if (!*a) { 374 unlockcaller=true; 375 break; 376 } 377 *rmvlock = lockpt = talloc(struct Lockrev); 378 lockpt->revno = a; 379 lockpt->nextrev = 0; 380 rmvlock = &lockpt->nextrev; 381 curlock = rmnewlocklst(lockpt->revno); 382 break; 383 384 case 'L': /* set strict locking */ 385 if (strict_selected) { 386 if (!strictlock) /* Already selected -U? */ 387 warn("-U overridden by -L"); 388 } 389 strictlock = true; 390 strict_selected = true; 391 break; 392 393 case 'U': /* release strict locking */ 394 if (strict_selected) { 395 if (strictlock) /* Already selected -L? */ 396 warn("-L overridden by -U"); 397 } 398 strict_selected = true; 399 break; 400 401 case 'n': /* add new association: error, if name exists */ 402 if (!*a) { 403 error("missing symbolic name after -n"); 404 break; 405 } 406 getassoclst(false, (*argv)+1); 407 break; 408 409 case 'N': /* add or change association */ 410 if (!*a) { 411 error("missing symbolic name after -N"); 412 break; 413 } 414 getassoclst(true, (*argv)+1); 415 break; 416 417 case 'm': /* change log message */ 418 getmessage(a); 419 break; 420 421 case 'M': /* do not send mail */ 422 suppress_mail = true; 423 break; 424 425 case 'o': /* delete revisions */ 426 if (delrev.strt) redefined('o'); 427 if (!*a) { 428 error("missing revision range after -o"); 429 break; 430 } 431 getdelrev( (*argv)+1 ); 432 break; 433 434 case 's': /* change state attribute of a revision */ 435 if (!*a) { 436 error("state missing after -s"); 437 break; 438 } 439 getstates( (*argv)+1); 440 break; 441 442 case 't': /* change descriptive text */ 443 textflag=true; 444 if (*a) { 445 if (textfile) redefined('t'); 446 textfile = a; 447 } 448 break; 449 450 case 'T': /* do not update last-mod time for minor changes */ 451 if (*a) 452 goto unknown; 453 Ttimeflag = true; 454 break; 455 456 case 'I': 457 interactiveflag = true; 458 break; 459 460 case 'q': 461 quietflag = true; 462 break; 463 464 case 'x': 465 suffixes = a; 466 break; 467 468 case 'V': 469 setRCSversion(*argv); 470 break; 471 472 case 'z': 473 zone_set(a); 474 break; 475 476 case 'k': /* set keyword expand mode */ 477 if (0 <= expmode) redefined('k'); 478 if (0 <= (expmode = str2expmode(a))) 479 break; 480 /* fall into */ 481 default: 482 unknown: 483 error("unknown option: %s%s", *argv, cmdusage); 484 }; 485 } /* end processing of options */ 486 487 /* Now handle all pathnames. */ 488 if (nerror) cleanup(); 489 else if (argc < 1) faterror("no input file%s", cmdusage); 490 else for (; 0 < argc; cleanup(), ++argv, --argc) { 491 492 ffree(); 493 494 if ( initflag ) { 495 switch (pairnames(argc, argv, rcswriteopen, false, false)) { 496 case -1: break; /* not exist; ok */ 497 case 0: continue; /* error */ 498 case 1: rcserror("already exists"); 499 continue; 500 } 501 } 502 else { 503 switch (pairnames(argc, argv, rcswriteopen, true, false)) { 504 case -1: continue; /* not exist */ 505 case 0: continue; /* errors */ 506 case 1: break; /* file exists; ok*/ 507 } 508 } 509 510 511 /* 512 * RCSname contains the name of the RCS file, and 513 * workname contains the name of the working file. 514 * if !initflag, finptr contains the file descriptor for the 515 * RCS file. The admin node is initialized. 516 */ 517 518 diagnose("RCS file: %s\n", RCSname); 519 520 changed = initflag | textflag; 521 keepRCStime = Ttimeflag; 522 if (!initflag) { 523 if (!checkaccesslist()) continue; 524 gettree(); /* Read the delta tree. */ 525 } 526 527 /* update admin. node */ 528 if (strict_selected) { 529 changed |= StrictLocks ^ strictlock; 530 StrictLocks = strictlock; 531 } 532 if ( 533 commsyml && 534 ( 535 commsymlen != Comment.size || 536 memcmp(commsyml, Comment.string, commsymlen) != 0 537 ) 538 ) { 539 Comment.string = commsyml; 540 Comment.size = strlen(commsyml); 541 changed = true; 542 } 543 if (0 <= expmode && Expand != expmode) { 544 Expand = expmode; 545 changed = true; 546 } 547 548 /* update default branch */ 549 if (branchflag && expandsym(branchsym, &branchnum)) { 550 if (countnumflds(branchnum.string)) { 551 if (cmpnum(Dbranch, branchnum.string) != 0) { 552 Dbranch = branchnum.string; 553 changed = true; 554 } 555 } else 556 if (Dbranch) { 557 Dbranch = 0; 558 changed = true; 559 } 560 } 561 562 changed |= doaccess(); /* Update access list. */ 563 564 changed |= doassoc(); /* Update association list. */ 565 566 changed |= dolocks(); /* Update locks. */ 567 568 changed |= domessages(); /* Update log messages. */ 569 570 /* update state attribution */ 571 if (chgheadstate) { 572 /* change state of default branch or head */ 573 if (!Dbranch) { 574 if (!Head) 575 rcswarn("can't change states in an empty tree"); 576 else if (strcmp(Head->state, headstate) != 0) { 577 Head->state = headstate; 578 changed = true; 579 } 580 } else 581 changed |= rcs_setstate(Dbranch,headstate); 582 } 583 for (curstate = statelst; curstate; curstate = curstate->nextstatus) 584 changed |= rcs_setstate(curstate->revno,curstate->status); 585 586 cuthead = cuttail = 0; 587 if (delrev.strt && removerevs()) { 588 /* rebuild delta tree if some deltas are deleted */ 589 if ( cuttail ) 590 VOID genrevs( 591 cuttail->num, (char *)0, (char *)0, (char *)0, 592 &gendeltas 593 ); 594 buildtree(); 595 changed = true; 596 keepRCStime = false; 597 } 598 599 if (nerror) 600 continue; 601 602 putadmin(); 603 if ( Head ) 604 puttree(Head, frewrite); 605 putdesc(textflag,textfile); 606 607 if ( Head) { 608 if (delrev.strt || messagelst) { 609 if (!cuttail || buildeltatext(gendeltas)) { 610 advise_access(finptr, MADV_SEQUENTIAL); 611 scanlogtext((struct hshentry *)0, false); 612 /* copy rest of delta text nodes that are not deleted */ 613 changed = true; 614 } 615 } 616 } 617 618 if (initflag) { 619 /* Adjust things for donerewrite's sake. */ 620 if (stat(workname, &RCSstat) != 0) { 621# if bad_creat0 622 mode_t m = umask(0); 623 (void) umask(m); 624 RCSstat.st_mode = (S_IRUSR|S_IRGRP|S_IROTH) & ~m; 625# else 626 changed = -1; 627# endif 628 } 629 RCSstat.st_nlink = 0; 630 keepRCStime = false; 631 } 632 if (donerewrite(changed, 633 keepRCStime ? RCSstat.st_mtime : (time_t)-1 634 ) != 0) 635 break; 636 637 diagnose("done\n"); 638 } 639 640 tempunlink(); 641 exitmain(exitstatus); 642} /* end of main (rcs) */ 643 644 static void 645cleanup() 646{ 647 if (nerror) exitstatus = EXIT_FAILURE; 648 Izclose(&finptr); 649 Ozclose(&fcopy); 650 ORCSclose(); 651 dirtempunlink(); 652} 653 654 void 655exiterr() 656{ 657 ORCSerror(); 658 dirtempunlink(); 659 tempunlink(); 660 _exit(EXIT_FAILURE); 661} 662 663 664 static void 665getassoclst(flag, sp) 666int flag; 667char * sp; 668/* Function: associate a symbolic name to a revision or branch, */ 669/* and store in assoclst */ 670 671{ 672 struct Symrev * pt; 673 char const *temp; 674 int c; 675 676 while ((c = *++sp) == ' ' || c == '\t' || c =='\n') 677 continue; 678 temp = sp; 679 sp = checksym(sp, ':'); /* check for invalid symbolic name */ 680 c = *sp; *sp = '\0'; 681 while( c == ' ' || c == '\t' || c == '\n') c = *++sp; 682 683 if ( c != ':' && c != '\0') { 684 error("invalid string %s after option -n or -N",sp); 685 return; 686 } 687 688 pt = talloc(struct Symrev); 689 pt->ssymbol = temp; 690 pt->override = flag; 691 if (c == '\0') /* delete symbol */ 692 pt->revno = 0; 693 else { 694 while ((c = *++sp) == ' ' || c == '\n' || c == '\t') 695 continue; 696 pt->revno = sp; 697 } 698 pt->nextsym = 0; 699 *nextassoc = pt; 700 nextassoc = &pt->nextsym; 701} 702 703 704 static void 705getchaccess(login, command) 706 char const *login; 707 enum changeaccess command; 708{ 709 register struct chaccess *pt; 710 711 pt = talloc(struct chaccess); 712 pt->login = login; 713 pt->command = command; 714 pt->nextchaccess = 0; 715 *nextchaccess = pt; 716 nextchaccess = &pt->nextchaccess; 717} 718 719 720 721 static void 722getaccessor(opt, command) 723 char *opt; 724 enum changeaccess command; 725/* Function: get the accessor list of options -e and -a, */ 726/* and store in chaccess */ 727 728 729{ 730 register c; 731 register char *sp; 732 733 sp = opt; 734 while ((c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') 735 continue; 736 if ( c == '\0') { 737 if (command == erase && sp-opt == 1) { 738 getchaccess((char*)0, command); 739 return; 740 } 741 error("missing login name after option -a or -e"); 742 return; 743 } 744 745 while( c != '\0') { 746 getchaccess(sp, command); 747 sp = checkid(sp,','); 748 c = *sp; *sp = '\0'; 749 while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp); 750 } 751} 752 753 754 static void 755getmessage(option) 756 char *option; 757{ 758 struct Message *pt; 759 struct cbuf cb; 760 char *m; 761 762 if (!(m = strchr(option, ':'))) { 763 error("-m option lacks revision number"); 764 return; 765 } 766 *m++ = 0; 767 cb = cleanlogmsg(m, strlen(m)); 768 if (!cb.size) { 769 error("-m option lacks log message"); 770 return; 771 } 772 pt = talloc(struct Message); 773 pt->revno = option; 774 pt->message = cb; 775 pt->nextmessage = 0; 776 *nextmessage = pt; 777 nextmessage = &pt->nextmessage; 778} 779 780 781 static void 782getstates(sp) 783char *sp; 784/* Function: get one state attribute and the corresponding */ 785/* revision and store in statelst */ 786 787{ 788 char const *temp; 789 struct Status *pt; 790 register c; 791 792 while ((c = *++sp) ==' ' || c == '\t' || c == '\n') 793 continue; 794 temp = sp; 795 sp = checkid(sp,':'); /* check for invalid state attribute */ 796 c = *sp; *sp = '\0'; 797 while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp; 798 799 if ( c == '\0' ) { /* change state of def. branch or Head */ 800 chgheadstate = true; 801 headstate = temp; 802 return; 803 } 804 else if ( c != ':' ) { 805 error("missing ':' after state in option -s"); 806 return; 807 } 808 809 while ((c = *++sp) == ' ' || c == '\t' || c == '\n') 810 continue; 811 pt = talloc(struct Status); 812 pt->status = temp; 813 pt->revno = sp; 814 pt->nextstatus = 0; 815 *nextstate = pt; 816 nextstate = &pt->nextstatus; 817} 818 819 820 821 static void 822getdelrev(sp) 823char *sp; 824/* Function: get revision range or branch to be deleted, */ 825/* and place in delrev */ 826{ 827 int c; 828 struct delrevpair *pt; 829 int separator; 830 831 pt = &delrev; 832 while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t') 833 continue; 834 835 /* Support old ambiguous '-' syntax; this will go away. */ 836 if (strchr(sp,':')) 837 separator = ':'; 838 else { 839 if (strchr(sp,'-') && VERSION(5) <= RCSversion) 840 warn("`-' is obsolete in `-o%s'; use `:' instead", sp); 841 separator = '-'; 842 } 843 844 if (c == separator) { /* -o:rev */ 845 while ((c = (*++sp)) == ' ' || c == '\n' || c == '\t') 846 continue; 847 pt->strt = sp; pt->code = 1; 848 while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp); 849 *sp = '\0'; 850 pt->end = 0; 851 return; 852 } 853 else { 854 pt->strt = sp; 855 while( c != ' ' && c != '\n' && c != '\t' && c != '\0' 856 && c != separator ) c = *++sp; 857 *sp = '\0'; 858 while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp; 859 if ( c == '\0' ) { /* -o rev or branch */ 860 pt->code = 0; 861 pt->end = 0; 862 return; 863 } 864 if (c != separator) { 865 error("invalid range %s %s after -o", pt->strt, sp); 866 } 867 while ((c = *++sp) == ' ' || c == '\n' || c == '\t') 868 continue; 869 if (!c) { /* -orev: */ 870 pt->code = 2; 871 pt->end = 0; 872 return; 873 } 874 } 875 /* -orev1:rev2 */ 876 pt->end = sp; pt->code = 3; 877 while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp; 878 *sp = '\0'; 879} 880 881 882 883 884 static void 885scanlogtext(delta,edit) 886 struct hshentry *delta; 887 int edit; 888/* Function: Scans delta text nodes up to and including the one given 889 * by delta, or up to last one present, if !delta. 890 * For the one given by delta (if delta), the log message is saved into 891 * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied. 892 * Assumes the initial lexeme must be read in first. 893 * Does not advance nexttok after it is finished, except if !delta. 894 */ 895{ 896 struct hshentry const *nextdelta; 897 struct cbuf cb; 898 899 for (;;) { 900 foutptr = 0; 901 if (eoflex()) { 902 if(delta) 903 rcsfaterror("can't find delta for revision %s", 904 delta->num 905 ); 906 return; /* no more delta text nodes */ 907 } 908 nextlex(); 909 if (!(nextdelta=getnum())) 910 fatserror("delta number corrupted"); 911 if (nextdelta->selector) { 912 foutptr = frewrite; 913 aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog); 914 } 915 getkeystring(Klog); 916 if (nextdelta == cuttail) { 917 cb = savestring(&curlogbuf); 918 if (!delta->log.string) 919 delta->log = cleanlogmsg(curlogbuf.string, cb.size); 920 nextlex(); 921 delta->igtext = getphrases(Ktext); 922 } else { 923 if (nextdelta->log.string && nextdelta->selector) { 924 foutptr = 0; 925 readstring(); 926 foutptr = frewrite; 927 putstring(foutptr, false, nextdelta->log, true); 928 afputc(nextc, foutptr); 929 } else 930 readstring(); 931 ignorephrases(Ktext); 932 } 933 getkeystring(Ktext); 934 935 if (delta==nextdelta) 936 break; 937 readstring(); /* skip over it */ 938 939 } 940 /* got the one we're looking for */ 941 if (edit) 942 editstring((struct hshentry*)0); 943 else 944 enterstring(); 945} 946 947 948 949 static struct Lockrev ** 950rmnewlocklst(which) 951 char const *which; 952/* Remove lock to revision WHICH from newlocklst. */ 953{ 954 struct Lockrev *pt, **pre; 955 956 pre = &newlocklst; 957 while ((pt = *pre)) 958 if (strcmp(pt->revno, which) != 0) 959 pre = &pt->nextrev; 960 else { 961 *pre = pt->nextrev; 962 tfree(pt); 963 } 964 return pre; 965} 966 967 968 969 static int 970doaccess() 971{ 972 register struct chaccess *ch; 973 register struct access **p, *t; 974 register int changed = false; 975 976 for (ch = chaccess; ch; ch = ch->nextchaccess) { 977 switch (ch->command) { 978 case erase: 979 if (!ch->login) { 980 if (AccessList) { 981 AccessList = 0; 982 changed = true; 983 } 984 } else 985 for (p = &AccessList; (t = *p); p = &t->nextaccess) 986 if (strcmp(ch->login, t->login) == 0) { 987 *p = t->nextaccess; 988 changed = true; 989 break; 990 } 991 break; 992 case append: 993 for (p = &AccessList; ; p = &t->nextaccess) 994 if (!(t = *p)) { 995 *p = t = ftalloc(struct access); 996 t->login = ch->login; 997 t->nextaccess = 0; 998 changed = true; 999 break; 1000 } else if (strcmp(ch->login, t->login) == 0) 1001 break; 1002 break; 1003 } 1004 } 1005 return changed; 1006} 1007 1008 1009 static int 1010sendmail(Delta, who) 1011 char const *Delta, *who; 1012/* Function: mail to who, informing him that his lock on delta was 1013 * broken by caller. Ask first whether to go ahead. Return false on 1014 * error or if user decides not to break the lock. 1015 */ 1016{ 1017#ifdef SENDMAIL 1018 char const *messagefile; 1019 int old1, old2, c, status; 1020 FILE * mailmess; 1021#endif 1022 1023 1024 aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who); 1025 if (suppress_mail) 1026 return true; 1027 if (!yesorno(false, "Do you want to break the lock? [ny](n): ")) 1028 return false; 1029 1030 /* go ahead with breaking */ 1031#ifdef SENDMAIL 1032 messagefile = maketemp(0); 1033 if (!(mailmess = fopenSafer(messagefile, "w+"))) { 1034 efaterror(messagefile); 1035 } 1036 1037 aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n", 1038 basefilename(RCSname), Delta, getfullRCSname(), getcaller() 1039 ); 1040 aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr); 1041 eflush(); 1042 1043 old1 = '\n'; old2 = ' '; 1044 for (; ;) { 1045 c = getcstdin(); 1046 if (feof(stdin)) { 1047 aprintf(mailmess, "%c\n", old1); 1048 break; 1049 } 1050 else if ( c == '\n' && old1 == '.' && old2 == '\n') 1051 break; 1052 else { 1053 afputc(old1, mailmess); 1054 old2 = old1; old1 = c; 1055 if (c == '\n') { 1056 aputs(">> ", stderr); 1057 eflush(); 1058 } 1059 } 1060 } 1061 Orewind(mailmess); 1062 aflush(mailmess); 1063 status = run(fileno(mailmess), (char*)0, SENDMAIL, who, (char*)0); 1064 Ozclose(&mailmess); 1065 if (status == 0) 1066 return true; 1067 warn("Mail failed."); 1068#endif 1069 warn("Mail notification of broken locks is not available."); 1070 warn("Please tell `%s' why you broke the lock.", who); 1071 return(true); 1072} 1073 1074 1075 1076 static int 1077breaklock(delta) 1078 struct hshentry const *delta; 1079/* function: Finds the lock held by caller on delta, 1080 * and removes it. 1081 * Sends mail if a lock different from the caller's is broken. 1082 * Prints an error message if there is no such lock or error. 1083 */ 1084{ 1085 register struct rcslock *next, **trail; 1086 char const *num; 1087 1088 num=delta->num; 1089 for (trail = &Locks; (next = *trail); trail = &next->nextlock) 1090 if (strcmp(num, next->delta->num) == 0) { 1091 if ( 1092 strcmp(getcaller(),next->login) != 0 1093 && !sendmail(num, next->login) 1094 ) { 1095 rcserror("revision %s still locked by %s", 1096 num, next->login 1097 ); 1098 return false; 1099 } 1100 diagnose("%s unlocked\n", next->delta->num); 1101 *trail = next->nextlock; 1102 next->delta->lockedby = 0; 1103 return true; 1104 } 1105 rcserror("no lock set on revision %s", num); 1106 return false; 1107} 1108 1109 1110 1111 static struct hshentry * 1112searchcutpt(object, length, store) 1113 char const *object; 1114 int length; 1115 struct hshentries *store; 1116/* Function: Search store and return entry with number being object. */ 1117/* cuttail = 0, if the entry is Head; otherwise, cuttail */ 1118/* is the entry point to the one with number being object */ 1119 1120{ 1121 cuthead = 0; 1122 while (compartial(store->first->num, object, length)) { 1123 cuthead = store->first; 1124 store = store->rest; 1125 } 1126 return store->first; 1127} 1128 1129 1130 1131 static int 1132branchpoint(strt, tail) 1133struct hshentry *strt, *tail; 1134/* Function: check whether the deltas between strt and tail */ 1135/* are locked or branch point, return 1 if any is */ 1136/* locked or branch point; otherwise, return 0 and */ 1137/* mark deleted */ 1138 1139{ 1140 struct hshentry *pt; 1141 struct rcslock const *lockpt; 1142 1143 for (pt = strt; pt != tail; pt = pt->next) { 1144 if ( pt->branches ){ /* a branch point */ 1145 rcserror("can't remove branch point %s", pt->num); 1146 return true; 1147 } 1148 for (lockpt = Locks; lockpt; lockpt = lockpt->nextlock) 1149 if (lockpt->delta == pt) { 1150 rcserror("can't remove locked revision %s", pt->num); 1151 return true; 1152 } 1153 pt->selector = false; 1154 diagnose("deleting revision %s\n",pt->num); 1155 } 1156 return false; 1157} 1158 1159 1160 1161 static int 1162removerevs() 1163/* Function: get the revision range to be removed, and place the */ 1164/* first revision removed in delstrt, the revision before */ 1165/* delstrt in cuthead (0, if delstrt is head), and the */ 1166/* revision after the last removed revision in cuttail (0 */ 1167/* if the last is a leaf */ 1168 1169{ 1170 struct hshentry *target, *target2, *temp; 1171 int length; 1172 int cmp; 1173 1174 if (!expandsym(delrev.strt, &numrev)) return 0; 1175 target = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas); 1176 if ( ! target ) return 0; 1177 cmp = cmpnum(target->num, numrev.string); 1178 length = countnumflds(numrev.string); 1179 1180 if (delrev.code == 0) { /* -o rev or -o branch */ 1181 if (length & 1) 1182 temp=searchcutpt(target->num,length+1,gendeltas); 1183 else if (cmp) { 1184 rcserror("Revision %s doesn't exist.", numrev.string); 1185 return 0; 1186 } 1187 else 1188 temp = searchcutpt(numrev.string, length, gendeltas); 1189 cuttail = target->next; 1190 if ( branchpoint(temp, cuttail) ) { 1191 cuttail = 0; 1192 return 0; 1193 } 1194 delstrt = temp; /* first revision to be removed */ 1195 return 1; 1196 } 1197 1198 if (length & 1) { /* invalid branch after -o */ 1199 rcserror("invalid branch range %s after -o", numrev.string); 1200 return 0; 1201 } 1202 1203 if (delrev.code == 1) { /* -o -rev */ 1204 if ( length > 2 ) { 1205 temp = searchcutpt( target->num, length-1, gendeltas); 1206 cuttail = target->next; 1207 } 1208 else { 1209 temp = searchcutpt(target->num, length, gendeltas); 1210 cuttail = target; 1211 while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) ) 1212 cuttail = cuttail->next; 1213 } 1214 if ( branchpoint(temp, cuttail) ){ 1215 cuttail = 0; 1216 return 0; 1217 } 1218 delstrt = temp; 1219 return 1; 1220 } 1221 1222 if (delrev.code == 2) { /* -o rev- */ 1223 if ( length == 2 ) { 1224 temp = searchcutpt(target->num, 1,gendeltas); 1225 if (cmp) 1226 cuttail = target; 1227 else 1228 cuttail = target->next; 1229 } 1230 else { 1231 if (cmp) { 1232 cuthead = target; 1233 if ( !(temp = target->next) ) return 0; 1234 } 1235 else 1236 temp = searchcutpt(target->num, length, gendeltas); 1237 getbranchno(temp->num, &numrev); /* get branch number */ 1238 VOID genrevs(numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas); 1239 } 1240 if ( branchpoint( temp, cuttail ) ) { 1241 cuttail = 0; 1242 return 0; 1243 } 1244 delstrt = temp; 1245 return 1; 1246 } 1247 1248 /* -o rev1-rev2 */ 1249 if (!expandsym(delrev.end, &numrev)) return 0; 1250 if ( 1251 length != countnumflds(numrev.string) 1252 || (length>2 && compartial(numrev.string, target->num, length-1)) 1253 ) { 1254 rcserror("invalid revision range %s-%s", 1255 target->num, numrev.string 1256 ); 1257 return 0; 1258 } 1259 1260 target2 = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&gendeltas); 1261 if ( ! target2 ) return 0; 1262 1263 if ( length > 2) { /* delete revisions on branches */ 1264 if ( cmpnum(target->num, target2->num) > 0) { 1265 cmp = cmpnum(target2->num, numrev.string); 1266 temp = target; 1267 target = target2; 1268 target2 = temp; 1269 } 1270 if (cmp) { 1271 if ( ! cmpnum(target->num, target2->num) ) { 1272 rcserror("Revisions %s-%s don't exist.", 1273 delrev.strt, delrev.end 1274 ); 1275 return 0; 1276 } 1277 cuthead = target; 1278 temp = target->next; 1279 } 1280 else 1281 temp = searchcutpt(target->num, length, gendeltas); 1282 cuttail = target2->next; 1283 } 1284 else { /* delete revisions on trunk */ 1285 if ( cmpnum( target->num, target2->num) < 0 ) { 1286 temp = target; 1287 target = target2; 1288 target2 = temp; 1289 } 1290 else 1291 cmp = cmpnum(target2->num, numrev.string); 1292 if (cmp) { 1293 if ( ! cmpnum(target->num, target2->num) ) { 1294 rcserror("Revisions %s-%s don't exist.", 1295 delrev.strt, delrev.end 1296 ); 1297 return 0; 1298 } 1299 cuttail = target2; 1300 } 1301 else 1302 cuttail = target2->next; 1303 temp = searchcutpt(target->num, length, gendeltas); 1304 } 1305 if ( branchpoint(temp, cuttail) ) { 1306 cuttail = 0; 1307 return 0; 1308 } 1309 delstrt = temp; 1310 return 1; 1311} 1312 1313 1314 1315 static int 1316doassoc() 1317/* Add or delete (if !revno) association that is stored in assoclst. */ 1318{ 1319 char const *p; 1320 int changed = false; 1321 struct Symrev const *curassoc; 1322 struct assoc **pre, *pt; 1323 1324 /* add new associations */ 1325 for (curassoc = assoclst; curassoc; curassoc = curassoc->nextsym) { 1326 char const *ssymbol = curassoc->ssymbol; 1327 1328 if (!curassoc->revno) { /* delete symbol */ 1329 for (pre = &Symbols; ; pre = &pt->nextassoc) 1330 if (!(pt = *pre)) { 1331 rcswarn("can't delete nonexisting symbol %s", ssymbol); 1332 break; 1333 } else if (strcmp(pt->symbol, ssymbol) == 0) { 1334 *pre = pt->nextassoc; 1335 changed = true; 1336 break; 1337 } 1338 } 1339 else { 1340 if (curassoc->revno[0]) { 1341 p = 0; 1342 if (expandsym(curassoc->revno, &numrev)) 1343 p = fstr_save(numrev.string); 1344 } else if (!(p = tiprev())) 1345 rcserror("no latest revision to associate with symbol %s", 1346 ssymbol 1347 ); 1348 if (p) 1349 changed |= addsymbol(p, ssymbol, curassoc->override); 1350 } 1351 } 1352 return changed; 1353} 1354 1355 1356 1357 static int 1358dolocks() 1359/* Function: remove lock for caller or first lock if unlockcaller is set; 1360 * remove locks which are stored in rmvlocklst, 1361 * add new locks which are stored in newlocklst, 1362 * add lock for Dbranch or Head if lockhead is set. 1363 */ 1364{ 1365 struct Lockrev const *lockpt; 1366 struct hshentry *target; 1367 int changed = false; 1368 1369 if (unlockcaller) { /* find lock for caller */ 1370 if ( Head ) { 1371 if (Locks) { 1372 switch (findlock(true, &target)) { 1373 case 0: 1374 /* remove most recent lock */ 1375 changed |= breaklock(Locks->delta); 1376 break; 1377 case 1: 1378 diagnose("%s unlocked\n",target->num); 1379 changed = true; 1380 break; 1381 } 1382 } else { 1383 rcswarn("No locks are set."); 1384 } 1385 } else { 1386 rcswarn("can't unlock an empty tree"); 1387 } 1388 } 1389 1390 /* remove locks which are stored in rmvlocklst */ 1391 for (lockpt = rmvlocklst; lockpt; lockpt = lockpt->nextrev) 1392 if (expandsym(lockpt->revno, &numrev)) { 1393 target = genrevs(numrev.string, (char *)0, (char *)0, (char *)0, &gendeltas); 1394 if ( target ) 1395 if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 1396 rcserror("can't unlock nonexisting revision %s", 1397 lockpt->revno 1398 ); 1399 else 1400 changed |= breaklock(target); 1401 /* breaklock does its own diagnose */ 1402 } 1403 1404 /* add new locks which stored in newlocklst */ 1405 for (lockpt = newlocklst; lockpt; lockpt = lockpt->nextrev) 1406 changed |= setlock(lockpt->revno); 1407 1408 if (lockhead) /* lock default branch or head */ 1409 if (Dbranch) 1410 changed |= setlock(Dbranch); 1411 else if (Head) 1412 changed |= setlock(Head->num); 1413 else 1414 rcswarn("can't lock an empty tree"); 1415 return changed; 1416} 1417 1418 1419 1420 static int 1421setlock(rev) 1422 char const *rev; 1423/* Function: Given a revision or branch number, finds the corresponding 1424 * delta and locks it for caller. 1425 */ 1426{ 1427 struct hshentry *target; 1428 int r; 1429 1430 if (expandsym(rev, &numrev)) { 1431 target = genrevs(numrev.string, (char*)0, (char*)0, 1432 (char*)0, &gendeltas); 1433 if ( target ) 1434 if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 1435 rcserror("can't lock nonexisting revision %s", 1436 numrev.string 1437 ); 1438 else { 1439 if ((r = addlock(target, false)) < 0 && breaklock(target)) 1440 r = addlock(target, true); 1441 if (0 <= r) { 1442 if (r) 1443 diagnose("%s locked\n", target->num); 1444 return r; 1445 } 1446 } 1447 } 1448 return 0; 1449} 1450 1451 1452 static int 1453domessages() 1454{ 1455 struct hshentry *target; 1456 struct Message *p; 1457 int changed = false; 1458 1459 for (p = messagelst; p; p = p->nextmessage) 1460 if ( 1461 expandsym(p->revno, &numrev) && 1462 (target = genrevs( 1463 numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas 1464 )) 1465 ) { 1466 /* 1467 * We can't check the old log -- it's much later in the file. 1468 * We pessimistically assume that it changed. 1469 */ 1470 target->log = p->message; 1471 changed = true; 1472 } 1473 return changed; 1474} 1475 1476 1477 static int 1478rcs_setstate(rev,status) 1479 char const *rev, *status; 1480/* Function: Given a revision or branch number, finds the corresponding delta 1481 * and sets its state to status. 1482 */ 1483{ 1484 struct hshentry *target; 1485 1486 if (expandsym(rev, &numrev)) { 1487 target = genrevs(numrev.string, (char*)0, (char*)0, 1488 (char*)0, &gendeltas); 1489 if ( target ) 1490 if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string)) 1491 rcserror("can't set state of nonexisting revision %s", 1492 numrev.string 1493 ); 1494 else if (strcmp(target->state, status) != 0) { 1495 target->state = status; 1496 return true; 1497 } 1498 } 1499 return false; 1500} 1501 1502 1503 1504 1505 1506 static int 1507buildeltatext(deltas) 1508 struct hshentries const *deltas; 1509/* Function: put the delta text on frewrite and make necessary */ 1510/* change to delta text */ 1511{ 1512 register FILE *fcut; /* temporary file to rebuild delta tree */ 1513 char const *cutname; 1514 1515 fcut = 0; 1516 cuttail->selector = false; 1517 scanlogtext(deltas->first, false); 1518 if ( cuthead ) { 1519 cutname = maketemp(3); 1520 if (!(fcut = fopenSafer(cutname, FOPEN_WPLUS_WORK))) { 1521 efaterror(cutname); 1522 } 1523 1524 while (deltas->first != cuthead) { 1525 deltas = deltas->rest; 1526 scanlogtext(deltas->first, true); 1527 } 1528 1529 snapshotedit(fcut); 1530 Orewind(fcut); 1531 aflush(fcut); 1532 } 1533 1534 while (deltas->first != cuttail) 1535 scanlogtext((deltas = deltas->rest)->first, true); 1536 finishedit((struct hshentry*)0, (FILE*)0, true); 1537 Ozclose(&fcopy); 1538 1539 if (fcut) { 1540 char const *diffname = maketemp(0); 1541 char const *diffv[6 + !!OPEN_O_BINARY]; 1542 char const **diffp = diffv; 1543 *++diffp = DIFF; 1544 *++diffp = DIFFFLAGS; 1545# if OPEN_O_BINARY 1546 if (Expand == BINARY_EXPAND) 1547 *++diffp == "--binary"; 1548# endif 1549 *++diffp = "-"; 1550 *++diffp = resultname; 1551 *++diffp = 0; 1552 switch (runv(fileno(fcut), diffname, diffv)) { 1553 case DIFF_FAILURE: case DIFF_SUCCESS: break; 1554 default: rcsfaterror("diff failed"); 1555 } 1556 Ofclose(fcut); 1557 return putdtext(cuttail,diffname,frewrite,true); 1558 } else 1559 return putdtext(cuttail,resultname,frewrite,false); 1560} 1561 1562 1563 1564 static void 1565buildtree() 1566/* Function: actually removes revisions whose selector field */ 1567/* is false, and rebuilds the linkage of deltas. */ 1568/* asks for reconfirmation if deleting last revision*/ 1569{ 1570 struct hshentry * Delta; 1571 struct branchhead *pt, *pre; 1572 1573 if ( cuthead ) 1574 if ( cuthead->next == delstrt ) 1575 cuthead->next = cuttail; 1576 else { 1577 pre = pt = cuthead->branches; 1578 while( pt && pt->hsh != delstrt ) { 1579 pre = pt; 1580 pt = pt->nextbranch; 1581 } 1582 if ( cuttail ) 1583 pt->hsh = cuttail; 1584 else if ( pt == pre ) 1585 cuthead->branches = pt->nextbranch; 1586 else 1587 pre->nextbranch = pt->nextbranch; 1588 } 1589 else { 1590 if (!cuttail && !quietflag) { 1591 if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) { 1592 rcserror("No revision deleted"); 1593 Delta = delstrt; 1594 while( Delta) { 1595 Delta->selector = true; 1596 Delta = Delta->next; 1597 } 1598 return; 1599 } 1600 } 1601 Head = cuttail; 1602 } 1603 return; 1604} 1605 1606#if RCS_lint 1607/* This lets us lint everything all at once. */ 1608 1609char const cmdid[] = ""; 1610 1611#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();} 1612 1613 int 1614main(argc, argv) 1615 int argc; 1616 char **argv; 1617{ 1618 go(ciId, ciExit); 1619 go(coId, coExit); 1620 go(identId, identExit); 1621 go(mergeId, mergeExit); 1622 go(rcsId, exiterr); 1623 go(rcscleanId, rcscleanExit); 1624 go(rcsdiffId, rdiffExit); 1625 go(rcsmergeId, rmergeExit); 1626 go(rlogId, rlogExit); 1627 return 0; 1628} 1629#endif 1630