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