]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - gnu/usr.bin/rcs/rcs/rcs.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / gnu / usr.bin / rcs / rcs / rcs.c
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
645 cleanup()
646 {
647         if (nerror) exitstatus = EXIT_FAILURE;
648         Izclose(&finptr);
649         Ozclose(&fcopy);
650         ORCSclose();
651         dirtempunlink();
652 }
653
654         void
655 exiterr()
656 {
657         ORCSerror();
658         dirtempunlink();
659         tempunlink();
660         _exit(EXIT_FAILURE);
661 }
662
663
664         static void
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
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
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
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
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
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
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 **
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
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
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
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 *
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
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
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
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
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
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
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
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
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
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
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