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