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