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