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