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