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