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