co.c revision 11894
1/* Check out working files from revisions of RCS files.  */
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
7This file is part of RCS.
8
9RCS is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2, or (at your option)
12any later version.
13
14RCS is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with RCS; see the file COPYING.
21If not, write to the Free Software Foundation,
2259 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24Report problems and direct all questions to:
25
26    rcs-bugs@cs.purdue.edu
27
28*/
29
30/*
31 * $Log: co.c,v $
32 * Revision 5.18  1995/06/16 06:19:24  eggert
33 * Update FSF address.
34 *
35 * Revision 5.17  1995/06/01 16:23:43  eggert
36 * (main, preparejoin): Pass argument instead of using `join' static variable.
37 * (main): Add -kb.
38 *
39 * Revision 5.16  1994/03/17 14:05:48  eggert
40 * Move buffer-flushes out of critical sections, since they aren't critical.
41 * Use ORCSerror to clean up after a fatal error.  Remove lint.
42 * Specify subprocess input via file descriptor, not file name.
43 *
44 * Revision 5.15  1993/11/09 17:40:15  eggert
45 * -V now prints version on stdout and exits.  Don't print usage twice.
46 *
47 * Revision 5.14  1993/11/03 17:42:27  eggert
48 * Add -z.  Generate a value for the Name keyword.
49 * Don't arbitrarily limit the number of joins.
50 * Improve quality of diagnostics.
51 *
52 * Revision 5.13  1992/07/28  16:12:44  eggert
53 * Add -V.  Check that working and RCS files are distinct.
54 *
55 * Revision 5.12  1992/02/17  23:02:08  eggert
56 * Add -T.
57 *
58 * Revision 5.11  1992/01/24  18:44:19  eggert
59 * Add support for bad_creat0.  lint -> RCS_lint
60 *
61 * Revision 5.10  1992/01/06  02:42:34  eggert
62 * Update usage string.
63 *
64 * Revision 5.9  1991/10/07  17:32:46  eggert
65 * -k affects just working file, not RCS file.
66 *
67 * Revision 5.8  1991/08/19  03:13:55  eggert
68 * Warn before removing somebody else's file.
69 * Add -M.  Fix co -j bugs.  Tune.
70 *
71 * Revision 5.7  1991/04/21  11:58:15  eggert
72 * Ensure that working file is newer than RCS file after co -[lu].
73 * Add -x, RCSINIT, MS-DOS support.
74 *
75 * Revision 5.6  1990/12/04  05:18:38  eggert
76 * Don't checkaccesslist() unless necessary.
77 * Use -I for prompts and -q for diagnostics.
78 *
79 * Revision 5.5  1990/11/01  05:03:26  eggert
80 * Fix -j.  Add -I.
81 *
82 * Revision 5.4  1990/10/04  06:30:11  eggert
83 * Accumulate exit status across files.
84 *
85 * Revision 5.3  1990/09/11  02:41:09  eggert
86 * co -kv yields a readonly working file.
87 *
88 * Revision 5.2  1990/09/04  08:02:13  eggert
89 * Standardize yes-or-no procedure.
90 *
91 * Revision 5.0  1990/08/22  08:10:02  eggert
92 * Permit multiple locks by same user.  Add setuid support.
93 * Remove compile-time limits; use malloc instead.
94 * Permit dates past 1999/12/31.  Switch to GMT.
95 * Make lock and temp files faster and safer.
96 * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
97 *
98 * Revision 4.7  89/05/01  15:11:41  narten
99 * changed copyright header to reflect current distribution rules
100 *
101 * Revision 4.6  88/08/09  19:12:15  eggert
102 * Fix "co -d" core dump; rawdate wasn't always initialized.
103 * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint
104 *
105 * Revision 4.5  87/12/18  11:35:40  narten
106 * lint cleanups (from Guy Harris)
107 *
108 * Revision 4.4  87/10/18  10:20:53  narten
109 * Updating version numbers changes relative to 1.1, are actually
110 * relative to 4.2
111 *
112 * Revision 1.3  87/09/24  13:58:30  narten
113 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
114 * warnings)
115 *
116 * Revision 1.2  87/03/27  14:21:38  jenkins
117 * Port to suns
118 *
119 * Revision 4.2  83/12/05  13:39:48  wft
120 * made rewriteflag external.
121 *
122 * Revision 4.1  83/05/10  16:52:55  wft
123 * Added option -u and -f.
124 * Added handling of default branch.
125 * Replaced getpwuid() with getcaller().
126 * Removed calls to stat(); now done by pairfilenames().
127 * Changed and renamed rmoldfile() to rmworkfile().
128 * Replaced catchints() calls with restoreints(), unlink()--link() with rename();
129 *
130 * Revision 3.7  83/02/15  15:27:07  wft
131 * Added call to fastcopy() to copy remainder of RCS file.
132 *
133 * Revision 3.6  83/01/15  14:37:50  wft
134 * Added ignoring of interrupts while RCS file is renamed; this avoids
135 * deletion of RCS files during the unlink/link window.
136 *
137 * Revision 3.5  82/12/08  21:40:11  wft
138 * changed processing of -d to use DATEFORM; removed actual from
139 * call to preparejoin; re-fixed printing of done at the end.
140 *
141 * Revision 3.4  82/12/04  18:40:00  wft
142 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
143 * Fixed printing of "done".
144 *
145 * Revision 3.3  82/11/28  22:23:11  wft
146 * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
147 * %02d with %.2d, mode generation for working file with WORKMODE.
148 * Fixed nil printing. Fixed -j combined with -l and -p, and exit
149 * for non-existing revisions in preparejoin().
150 *
151 * Revision 3.2  82/10/18  20:47:21  wft
152 * Mode of working file is now maintained even for co -l, but write permission
153 * is removed.
154 * The working file inherits its mode from the RCS file, plus write permission
155 * for the owner. The write permission is not given if locking is strict and
156 * co does not lock.
157 * An existing working file without write permission is deleted automatically.
158 * Otherwise, co asks (empty answer: abort co).
159 * Call to getfullRCSname() added, check for write error added, call
160 * for getlogin() fixed.
161 *
162 * Revision 3.1  82/10/13  16:01:30  wft
163 * fixed type of variables receiving from getc() (char -> int).
164 * removed unused variables.
165 */
166
167
168
169
170#include "rcsbase.h"
171
172static char *addjoin P((char*));
173static char const *getancestor P((char const*,char const*));
174static int buildjoin P((char const*));
175static int preparejoin P((char*));
176static int rmlock P((struct hshentry const*));
177static int rmworkfile P((void));
178static void cleanup P((void));
179
180static char const quietarg[] = "-q";
181
182static char const *expandarg, *suffixarg, *versionarg, *zonearg;
183static char const **joinlist; /* revisions to be joined */
184static int joinlength;
185static FILE *neworkptr;
186static int exitstatus;
187static int forceflag;
188static int lastjoin;			/* index of last element in joinlist  */
189static int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */
190static int mtimeflag;
191static struct hshentries *gendeltas;	/* deltas to be generated	*/
192static struct hshentry *targetdelta;	/* final delta to be generated	*/
193static struct stat workstat;
194
195mainProg(coId, "co", "$Id: co.c,v 5.18 1995/06/16 06:19:24 eggert Exp $")
196{
197	static char const cmdusage[] =
198		"\nco usage: co -{fIlMpqru}[rev] -ddate -jjoins -ksubst -sstate -T -w[who] -Vn -xsuff -zzone file ...";
199
200	char *a, *joinflag, **newargv;
201	char const *author, *date, *rev, *state;
202	char const *joinname, *newdate, *neworkname;
203	int changelock;  /* 1 if a lock has been changed, -1 if error */
204	int expmode, r, tostdout, workstatstat;
205	int Ttimeflag;
206	struct buf numericrev;	/* expanded revision number	*/
207	char finaldate[datesize];
208#	if OPEN_O_BINARY
209		int stdout_mode = 0;
210#	endif
211
212	setrid();
213	author = date = rev = state = 0;
214	joinflag = 0;
215	bufautobegin(&numericrev);
216	expmode = -1;
217	suffixes = X_DEFAULT;
218	tostdout = false;
219	Ttimeflag = false;
220
221	argc = getRCSINIT(argc, argv, &newargv);
222	argv = newargv;
223	while (a = *++argv,  0<--argc && *a++=='-') {
224		switch (*a++) {
225
226                case 'r':
227		revno:
228			if (*a) {
229				if (rev) warn("redefinition of revision number");
230				rev = a;
231                        }
232                        break;
233
234		case 'f':
235			forceflag=true;
236			goto revno;
237
238                case 'l':
239			if (lockflag < 0) {
240				warn("-u overridden by -l.");
241                        }
242			lockflag = 1;
243                        goto revno;
244
245                case 'u':
246			if (0 < lockflag) {
247				warn("-l overridden by -u.");
248                        }
249			lockflag = -1;
250                        goto revno;
251
252                case 'p':
253			tostdout = true;
254                        goto revno;
255
256		case 'I':
257			interactiveflag = true;
258			goto revno;
259
260                case 'q':
261                        quietflag=true;
262                        goto revno;
263
264                case 'd':
265			if (date)
266				redefined('d');
267			str2date(a, finaldate);
268                        date=finaldate;
269                        break;
270
271                case 'j':
272			if (*a) {
273				if (joinflag) redefined('j');
274				joinflag = a;
275                        }
276                        break;
277
278		case 'M':
279			mtimeflag = true;
280			goto revno;
281
282                case 's':
283			if (*a) {
284				if (state) redefined('s');
285				state = a;
286                        }
287                        break;
288
289		case 'T':
290			if (*a)
291				goto unknown;
292			Ttimeflag = true;
293			break;
294
295                case 'w':
296			if (author) redefined('w');
297			if (*a)
298				author = a;
299			else
300				author = getcaller();
301                        break;
302
303		case 'x':
304			suffixarg = *argv;
305			suffixes = a;
306			break;
307
308		case 'V':
309			versionarg = *argv;
310			setRCSversion(versionarg);
311			break;
312
313		case 'z':
314			zonearg = *argv;
315			zone_set(a);
316			break;
317
318		case 'k':    /*  set keyword expand mode  */
319			expandarg = *argv;
320			if (0 <= expmode) redefined('k');
321			if (0 <= (expmode = str2expmode(a)))
322			    break;
323			/* fall into */
324                default:
325		unknown:
326			error("unknown option: %s%s", *argv, cmdusage);
327
328                };
329        } /* end of option processing */
330
331	/* Now handle all pathnames.  */
332	if (nerror) cleanup();
333	else if (argc < 1) faterror("no input file%s", cmdusage);
334	else for (;  0 < argc;  cleanup(), ++argv, --argc) {
335	ffree();
336
337	if (pairnames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false)  <=  0)
338		continue;
339
340	/*
341	 * RCSname contains the name of the RCS file, and finptr
342	 * points at it.  workname contains the name of the working file.
343	 * Also, RCSstat has been set.
344         */
345	diagnose("%s  -->  %s\n", RCSname, tostdout?"standard output":workname);
346
347	workstatstat = -1;
348	if (tostdout) {
349#		if OPEN_O_BINARY
350		    int newmode = Expand==BINARY_EXPAND ? OPEN_O_BINARY : 0;
351		    if (stdout_mode != newmode) {
352			stdout_mode = newmode;
353			oflush();
354			VOID setmode(STDOUT_FILENO, newmode);
355		    }
356#		endif
357		neworkname = 0;
358		neworkptr = workstdout = stdout;
359	} else {
360		workstatstat = stat(workname, &workstat);
361		if (workstatstat == 0  &&  same_file(RCSstat, workstat, 0)) {
362			rcserror("RCS file is the same as working file %s.",
363				workname
364			);
365			continue;
366		}
367		neworkname = makedirtemp(1);
368		if (!(neworkptr = fopenSafer(neworkname, FOPEN_W_WORK))) {
369			if (errno == EACCES)
370			    workerror("permission denied on parent directory");
371			else
372			    eerror(neworkname);
373			continue;
374		}
375	}
376
377        gettree();  /* reads in the delta tree */
378
379	if (!Head) {
380                /* no revisions; create empty file */
381		diagnose("no revisions present; generating empty revision 0.0\n");
382		if (lockflag)
383			warn(
384				"no revisions, so nothing can be %slocked",
385				lockflag < 0 ? "un" : ""
386			);
387		Ozclose(&fcopy);
388		if (workstatstat == 0)
389			if (!rmworkfile()) continue;
390		changelock = 0;
391		newdate = 0;
392        } else {
393		int locks = lockflag ? findlock(false, &targetdelta) : 0;
394		if (rev) {
395                        /* expand symbolic revision number */
396			if (!expandsym(rev, &numericrev))
397                                continue;
398		} else {
399			switch (locks) {
400			    default:
401				continue;
402			    case 0:
403				bufscpy(&numericrev, Dbranch?Dbranch:"");
404				break;
405			    case 1:
406				bufscpy(&numericrev, targetdelta->num);
407				break;
408			}
409		}
410                /* get numbers of deltas to be generated */
411		if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas)))
412                        continue;
413                /* check reservations */
414		changelock =
415			lockflag < 0 ?
416				rmlock(targetdelta)
417			: lockflag == 0 ?
418				0
419			:
420				addlock(targetdelta, true);
421
422		if (
423			changelock < 0
424			|| (changelock && !checkaccesslist())
425			|| dorewrite(lockflag, changelock) != 0
426		)
427			continue;
428
429		if (0 <= expmode)
430			Expand = expmode;
431		if (0 < lockflag  &&  Expand == VAL_EXPAND) {
432			rcserror("cannot combine -kv and -l");
433			continue;
434		}
435
436		if (joinflag && !preparejoin(joinflag))
437			continue;
438
439		diagnose("revision %s%s\n",targetdelta->num,
440			 0<lockflag ? " (locked)" :
441			 lockflag<0 ? " (unlocked)" : "");
442
443		/* Prepare to remove old working file if necessary.  */
444		if (workstatstat == 0)
445                        if (!rmworkfile()) continue;
446
447                /* skip description */
448                getdesc(false); /* don't echo*/
449
450		locker_expansion = 0 < lockflag;
451		targetdelta->name = namedrev(rev, targetdelta);
452		joinname = buildrevision(
453			gendeltas, targetdelta,
454			joinflag&&tostdout ? (FILE*)0 : neworkptr,
455			Expand < MIN_UNEXPAND
456		);
457#		if !large_memory
458			if (fcopy == neworkptr)
459				fcopy = 0;  /* Don't close it twice.  */
460#		endif
461		if_advise_access(changelock && gendeltas->first!=targetdelta,
462			finptr, MADV_SEQUENTIAL
463		);
464
465		if (donerewrite(changelock,
466			Ttimeflag ? RCSstat.st_mtime : (time_t)-1
467		) != 0)
468			continue;
469
470		if (changelock) {
471			locks += lockflag;
472			if (1 < locks)
473				rcswarn("You now have %d locks.", locks);
474		}
475
476		newdate = targetdelta->date;
477		if (joinflag) {
478			newdate = 0;
479			if (!joinname) {
480				aflush(neworkptr);
481				joinname = neworkname;
482			}
483			if (Expand == BINARY_EXPAND)
484				workerror("merging binary files");
485			if (!buildjoin(joinname))
486				continue;
487		}
488        }
489	if (!tostdout) {
490	    mode_t m = WORKMODE(RCSstat.st_mode,
491		!  (Expand==VAL_EXPAND  ||  (lockflag<=0 && StrictLocks))
492	    );
493	    time_t t = mtimeflag&&newdate ? date2time(newdate) : (time_t)-1;
494	    aflush(neworkptr);
495	    ignoreints();
496	    r = chnamemod(&neworkptr, neworkname, workname, 1, m, t);
497	    keepdirtemp(neworkname);
498	    restoreints();
499	    if (r != 0) {
500		eerror(workname);
501		error("see %s", neworkname);
502		continue;
503	    }
504	    diagnose("done\n");
505	}
506	}
507
508	tempunlink();
509	Ofclose(workstdout);
510	exitmain(exitstatus);
511
512}       /* end of main (co) */
513
514	static void
515cleanup()
516{
517	if (nerror) exitstatus = EXIT_FAILURE;
518	Izclose(&finptr);
519	ORCSclose();
520#	if !large_memory
521		if (fcopy!=workstdout) Ozclose(&fcopy);
522#	endif
523	if (neworkptr!=workstdout) Ozclose(&neworkptr);
524	dirtempunlink();
525}
526
527#if RCS_lint
528#	define exiterr coExit
529#endif
530	void
531exiterr()
532{
533	ORCSerror();
534	dirtempunlink();
535	tempunlink();
536	_exit(EXIT_FAILURE);
537}
538
539
540/*****************************************************************
541 * The following routines are auxiliary routines
542 *****************************************************************/
543
544	static int
545rmworkfile()
546/*
547 * Prepare to remove workname, if it exists, and if
548 * it is read-only.
549 * Otherwise (file writable):
550 *   if !quietmode asks the user whether to really delete it (default: fail);
551 *   otherwise failure.
552 * Returns true if permission is gotten.
553 */
554{
555	if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) {
556	    /* File is writable */
557	    if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ",
558			workname,
559			myself(workstat.st_uid) ? "" : ", and you do not own it"
560	    )) {
561		error(!quietflag && ttystdin()
562			? "checkout aborted"
563			: "writable %s exists; checkout aborted", workname);
564		return false;
565            }
566        }
567	/* Actual unlink is done later by caller. */
568	return true;
569}
570
571
572	static int
573rmlock(delta)
574	struct hshentry const *delta;
575/* Function: removes the lock held by caller on delta.
576 * Returns -1 if someone else holds the lock,
577 * 0 if there is no lock on delta,
578 * and 1 if a lock was found and removed.
579 */
580{       register struct rcslock * next, * trail;
581	char const *num;
582	struct rcslock dummy;
583        int whomatch, nummatch;
584
585        num=delta->num;
586        dummy.nextlock=next=Locks;
587        trail = &dummy;
588	while (next) {
589		whomatch = strcmp(getcaller(), next->login);
590                nummatch=strcmp(num,next->delta->num);
591                if ((whomatch==0) && (nummatch==0)) break;
592			/*found a lock on delta by caller*/
593                if ((whomatch!=0)&&(nummatch==0)) {
594                    rcserror("revision %s locked by %s; use co -r or rcs -u",
595			num, next->login
596		    );
597                    return -1;
598                }
599                trail=next;
600                next=next->nextlock;
601        }
602	if (next) {
603                /*found one; delete it */
604                trail->nextlock=next->nextlock;
605                Locks=dummy.nextlock;
606		next->delta->lockedby = 0;
607                return 1; /*success*/
608        } else  return 0; /*no lock on delta*/
609}
610
611
612
613
614/*****************************************************************
615 * The rest of the routines are for handling joins
616 *****************************************************************/
617
618
619	static char *
620addjoin(joinrev)
621	char *joinrev;
622/* Add joinrev's number to joinlist, yielding address of char past joinrev,
623 * or 0 if no such revision exists.
624 */
625{
626	register char *j;
627	register struct hshentry *d;
628	char terminator;
629	struct buf numrev;
630	struct hshentries *joindeltas;
631
632	j = joinrev;
633	for (;;) {
634	    switch (*j++) {
635		default:
636		    continue;
637		case 0:
638		case ' ': case '\t': case '\n':
639		case ':': case ',': case ';':
640		    break;
641	    }
642	    break;
643	}
644	terminator = *--j;
645	*j = 0;
646	bufautobegin(&numrev);
647	d = 0;
648	if (expandsym(joinrev, &numrev))
649	    d = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&joindeltas);
650	bufautoend(&numrev);
651	*j = terminator;
652	if (d) {
653		joinlist[++lastjoin] = d->num;
654		return j;
655	}
656	return 0;
657}
658
659	static int
660preparejoin(j)
661	register char *j;
662/* Parse join list J and place pointers to the
663 * revision numbers into joinlist.
664 */
665{
666        lastjoin= -1;
667        for (;;) {
668                while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
669                if (*j=='\0') break;
670                if (lastjoin>=joinlength-2) {
671		    joinlist =
672			(joinlength *= 2) == 0
673			? tnalloc(char const *, joinlength = 16)
674			: trealloc(char const *, joinlist, joinlength);
675                }
676		if (!(j = addjoin(j))) return false;
677                while ((*j==' ') || (*j=='\t')) j++;
678                if (*j == ':') {
679                        j++;
680                        while((*j==' ') || (*j=='\t')) j++;
681                        if (*j!='\0') {
682				if (!(j = addjoin(j))) return false;
683                        } else {
684				rcsfaterror("join pair incomplete");
685                        }
686                } else {
687                        if (lastjoin==0) { /* first pair */
688                                /* common ancestor missing */
689                                joinlist[1]=joinlist[0];
690                                lastjoin=1;
691                                /*derive common ancestor*/
692				if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1])))
693                                       return false;
694                        } else {
695				rcsfaterror("join pair incomplete");
696                        }
697                }
698        }
699	if (lastjoin < 1)
700		rcsfaterror("empty join");
701	return true;
702}
703
704
705
706	static char const *
707getancestor(r1, r2)
708	char const *r1, *r2;
709/* Yield the common ancestor of r1 and r2 if successful, 0 otherwise.
710 * Work reliably only if r1 and r2 are not branch numbers.
711 */
712{
713	static struct buf t1, t2;
714
715	int l1, l2, l3;
716	char const *r;
717
718	l1 = countnumflds(r1);
719	l2 = countnumflds(r2);
720	if ((2<l1 || 2<l2)  &&  cmpnum(r1,r2)!=0) {
721	    /* not on main trunk or identical */
722	    l3 = 0;
723	    while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
724		l3 += 2;
725	    /* This will terminate since r1 and r2 are not the same; see above. */
726	    if (l3==0) {
727		/* no common prefix; common ancestor on main trunk */
728		VOID partialno(&t1, r1, l1>2 ? 2 : l1);
729		VOID partialno(&t2, r2, l2>2 ? 2 : l2);
730		r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string;
731		if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0)
732			return r;
733	    } else if (cmpnumfld(r1, r2, l3+1)!=0)
734			return partialno(&t1,r1,l3);
735	}
736	rcserror("common ancestor of %s and %s undefined", r1, r2);
737	return 0;
738}
739
740
741
742	static int
743buildjoin(initialfile)
744	char const *initialfile;
745/* Function: merge pairs of elements in joinlist into initialfile
746 * If workstdout is set, copy result to stdout.
747 * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink().
748 */
749{
750	struct buf commarg;
751	struct buf subs;
752	char const *rev2, *rev3;
753        int i;
754	char const *cov[10], *mergev[11];
755	char const **p;
756
757	bufautobegin(&commarg);
758	bufautobegin(&subs);
759	rev2 = maketemp(0);
760	rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
761
762	cov[1] = CO;
763	/* cov[2] setup below */
764	p = &cov[3];
765	if (expandarg) *p++ = expandarg;
766	if (suffixarg) *p++ = suffixarg;
767	if (versionarg) *p++ = versionarg;
768	if (zonearg) *p++ = zonearg;
769	*p++ = quietarg;
770	*p++ = RCSname;
771	*p = 0;
772
773	mergev[1] = MERGE;
774	mergev[2] = mergev[4] = "-L";
775	/* rest of mergev setup below */
776
777        i=0;
778        while (i<lastjoin) {
779                /*prepare marker for merge*/
780                if (i==0)
781			bufscpy(&subs, targetdelta->num);
782		else {
783			bufscat(&subs, ",");
784			bufscat(&subs, joinlist[i-2]);
785			bufscat(&subs, ":");
786			bufscat(&subs, joinlist[i-1]);
787		}
788		diagnose("revision %s\n",joinlist[i]);
789		bufscpy(&commarg, "-p");
790		bufscat(&commarg, joinlist[i]);
791		cov[2] = commarg.string;
792		if (runv(-1, rev2, cov))
793			goto badmerge;
794		diagnose("revision %s\n",joinlist[i+1]);
795		bufscpy(&commarg, "-p");
796		bufscat(&commarg, joinlist[i+1]);
797		cov[2] = commarg.string;
798		if (runv(-1, rev3, cov))
799			goto badmerge;
800		diagnose("merging...\n");
801		mergev[3] = subs.string;
802		mergev[5] = joinlist[i+1];
803		p = &mergev[6];
804		if (quietflag) *p++ = quietarg;
805		if (lastjoin<=i+2 && workstdout) *p++ = "-p";
806		*p++ = initialfile;
807		*p++ = rev2;
808		*p++ = rev3;
809		*p = 0;
810		switch (runv(-1, (char*)0, mergev)) {
811		    case DIFF_FAILURE: case DIFF_SUCCESS:
812			break;
813		    default:
814			goto badmerge;
815		}
816                i=i+2;
817        }
818	bufautoend(&commarg);
819	bufautoend(&subs);
820        return true;
821
822    badmerge:
823	nerror++;
824	bufautoend(&commarg);
825	bufautoend(&subs);
826	return false;
827}
828