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