xref: /dragonfly/gnu/usr.bin/rcs/co/co.c (revision 984263bc)
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  * 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 
171 static char *addjoin P((char*));
172 static char const *getancestor P((char const*,char const*));
173 static int buildjoin P((char const*));
174 static int preparejoin P((char*));
175 static int rmlock P((struct hshentry const*));
176 static int rmworkfile P((void));
177 static void cleanup P((void));
178 
179 static char const quietarg[] = "-q";
180 
181 static char const *expandarg, *suffixarg, *versionarg, *zonearg;
182 static char const **joinlist; /* revisions to be joined */
183 static int joinlength;
184 static FILE *neworkptr;
185 static int exitstatus;
186 static int forceflag;
187 static int lastjoin;			/* index of last element in joinlist  */
188 static int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */
189 static int mtimeflag;
190 static struct hshentries *gendeltas;	/* deltas to be generated	*/
191 static struct hshentry *targetdelta;	/* final delta to be generated	*/
192 static struct stat workstat;
193 
194 mainProg(coId, "co", "$FreeBSD: src/gnu/usr.bin/rcs/co/co.c,v 1.10 1999/08/27 23:36:40 peter Exp $")
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 expand mode  */
318 			expandarg = *argv;
319 			if (0 <= expmode) redefined('k');
320 			if (0 <= (expmode = str2expmode(a)))
321 			    break;
322 			/* fall into */
323                 default:
324 		unknown:
325 			error("unknown option: %s%s", *argv, cmdusage);
326 
327                 };
328         } /* end of option processing */
329 
330 	/* Now handle all pathnames.  */
331 	if (nerror) cleanup();
332 	else if (argc < 1) faterror("no input file%s", cmdusage);
333 	else for (;  0 < argc;  cleanup(), ++argv, --argc) {
334 	ffree();
335 
336 	if (pairnames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false)  <=  0)
337 		continue;
338 
339 	/*
340 	 * RCSname contains the name of the RCS file, and finptr
341 	 * points at it.  workname contains the name of the working file.
342 	 * Also, RCSstat has been set.
343          */
344 	diagnose("%s  -->  %s\n", RCSname, tostdout?"standard output":workname);
345 
346 	workstatstat = -1;
347 	if (tostdout) {
348 #		if OPEN_O_BINARY
349 		    int newmode = Expand==BINARY_EXPAND ? OPEN_O_BINARY : 0;
350 		    if (stdout_mode != newmode) {
351 			stdout_mode = newmode;
352 			oflush();
353 			VOID setmode(STDOUT_FILENO, newmode);
354 		    }
355 #		endif
356 		neworkname = 0;
357 		neworkptr = workstdout = stdout;
358 	} else {
359 		workstatstat = stat(workname, &workstat);
360 		if (workstatstat == 0  &&  same_file(RCSstat, workstat, 0)) {
361 			rcserror("RCS file is the same as working file %s.",
362 				workname
363 			);
364 			continue;
365 		}
366 		neworkname = makedirtemp(1);
367 		if (!(neworkptr = fopenSafer(neworkname, FOPEN_W_WORK))) {
368 			if (errno == EACCES)
369 			    workerror("permission denied on parent directory");
370 			else
371 			    eerror(neworkname);
372 			continue;
373 		}
374 	}
375 
376         gettree();  /* reads in the delta tree */
377 
378 	if (!Head) {
379                 /* no revisions; create empty file */
380 		diagnose("no revisions present; generating empty revision 0.0\n");
381 		if (lockflag)
382 			warn(
383 				"no revisions, so nothing can be %slocked",
384 				lockflag < 0 ? "un" : ""
385 			);
386 		Ozclose(&fcopy);
387 		if (workstatstat == 0)
388 			if (!rmworkfile()) continue;
389 		changelock = 0;
390 		newdate = 0;
391         } else {
392 		int locks = lockflag ? findlock(false, &targetdelta) : 0;
393 		if (rev) {
394                         /* expand symbolic revision number */
395 			if (!expandsym(rev, &numericrev))
396                                 continue;
397 		} else {
398 			switch (locks) {
399 			    default:
400 				continue;
401 			    case 0:
402 				bufscpy(&numericrev, Dbranch?Dbranch:"");
403 				break;
404 			    case 1:
405 				bufscpy(&numericrev, targetdelta->num);
406 				break;
407 			}
408 		}
409                 /* get numbers of deltas to be generated */
410 		if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas)))
411                         continue;
412                 /* check reservations */
413 		changelock =
414 			lockflag < 0 ?
415 				rmlock(targetdelta)
416 			: lockflag == 0 ?
417 				0
418 			:
419 				addlock(targetdelta, true);
420 
421 		if (
422 			changelock < 0
423 			|| (changelock && !checkaccesslist())
424 			|| dorewrite(lockflag, changelock) != 0
425 		)
426 			continue;
427 
428 		if (0 <= expmode)
429 			Expand = expmode;
430 		if (0 < lockflag  &&  Expand == VAL_EXPAND) {
431 			rcserror("cannot combine -kv and -l");
432 			continue;
433 		}
434 
435 		if (joinflag && !preparejoin(joinflag))
436 			continue;
437 
438 		diagnose("revision %s%s\n",targetdelta->num,
439 			 0<lockflag ? " (locked)" :
440 			 lockflag<0 ? " (unlocked)" : "");
441 
442 		/* Prepare to remove old working file if necessary.  */
443 		if (workstatstat == 0)
444                         if (!rmworkfile()) continue;
445 
446                 /* skip description */
447                 getdesc(false); /* don't echo*/
448 
449 		locker_expansion = 0 < lockflag;
450 		targetdelta->name = namedrev(rev, targetdelta);
451 		joinname = buildrevision(
452 			gendeltas, targetdelta,
453 			joinflag&&tostdout ? (FILE*)0 : neworkptr,
454 			Expand < MIN_UNEXPAND
455 		);
456 #		if !large_memory
457 			if (fcopy == neworkptr)
458 				fcopy = 0;  /* Don't close it twice.  */
459 #		endif
460 		if_advise_access(changelock && gendeltas->first!=targetdelta,
461 			finptr, MADV_SEQUENTIAL
462 		);
463 
464 		if (donerewrite(changelock,
465 			Ttimeflag ? RCSstat.st_mtime : (time_t)-1
466 		) != 0)
467 			continue;
468 
469 		if (changelock) {
470 			locks += lockflag;
471 			if (1 < locks)
472 				rcswarn("You now have %d locks.", locks);
473 		}
474 
475 		newdate = targetdelta->date;
476 		if (joinflag) {
477 			newdate = 0;
478 			if (!joinname) {
479 				aflush(neworkptr);
480 				joinname = neworkname;
481 			}
482 			if (Expand == BINARY_EXPAND)
483 				workerror("merging binary files");
484 			if (!buildjoin(joinname))
485 				continue;
486 		}
487         }
488 	if (!tostdout) {
489 	    mode_t m = WORKMODE(RCSstat.st_mode,
490 		!  (Expand==VAL_EXPAND  ||  (lockflag<=0 && StrictLocks))
491 	    );
492 	    time_t t = mtimeflag&&newdate ? date2time(newdate) : (time_t)-1;
493 	    aflush(neworkptr);
494 	    ignoreints();
495 	    r = chnamemod(&neworkptr, neworkname, workname, 1, m, t);
496 	    keepdirtemp(neworkname);
497 	    restoreints();
498 	    if (r != 0) {
499 		eerror(workname);
500 		error("see %s", neworkname);
501 		continue;
502 	    }
503 	    diagnose("done\n");
504 	}
505 	}
506 
507 	tempunlink();
508 	Ofclose(workstdout);
509 	exitmain(exitstatus);
510 
511 }       /* end of main (co) */
512 
513 	static void
514 cleanup()
515 {
516 	if (nerror) exitstatus = EXIT_FAILURE;
517 	Izclose(&finptr);
518 	ORCSclose();
519 #	if !large_memory
520 		if (fcopy!=workstdout) Ozclose(&fcopy);
521 #	endif
522 	if (neworkptr!=workstdout) Ozclose(&neworkptr);
523 	dirtempunlink();
524 }
525 
526 #if RCS_lint
527 #	define exiterr coExit
528 #endif
529 	void
530 exiterr()
531 {
532 	ORCSerror();
533 	dirtempunlink();
534 	tempunlink();
535 	_exit(EXIT_FAILURE);
536 }
537 
538 
539 /*****************************************************************
540  * The following routines are auxiliary routines
541  *****************************************************************/
542 
543 	static int
544 rmworkfile()
545 /*
546  * Prepare to remove workname, if it exists, and if
547  * it is read-only.
548  * Otherwise (file writable):
549  *   if !quietmode asks the user whether to really delete it (default: fail);
550  *   otherwise failure.
551  * Returns true if permission is gotten.
552  */
553 {
554 	if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) {
555 	    /* File is writable */
556 	    if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ",
557 			workname,
558 			myself(workstat.st_uid) ? "" : ", and you do not own it"
559 	    )) {
560 		error(!quietflag && ttystdin()
561 			? "checkout aborted"
562 			: "writable %s exists; checkout aborted", workname);
563 		return false;
564             }
565         }
566 	/* Actual unlink is done later by caller. */
567 	return true;
568 }
569 
570 
571 	static int
572 rmlock(delta)
573 	struct hshentry const *delta;
574 /* Function: removes the lock held by caller on delta.
575  * Returns -1 if someone else holds the lock,
576  * 0 if there is no lock on delta,
577  * and 1 if a lock was found and removed.
578  */
579 {       register struct rcslock * next, * trail;
580 	char const *num;
581 	struct rcslock dummy;
582         int whomatch, nummatch;
583 
584         num=delta->num;
585         dummy.nextlock=next=Locks;
586         trail = &dummy;
587 	while (next) {
588 		whomatch = strcmp(getcaller(), next->login);
589                 nummatch=strcmp(num,next->delta->num);
590                 if ((whomatch==0) && (nummatch==0)) break;
591 			/*found a lock on delta by caller*/
592                 if ((whomatch!=0)&&(nummatch==0)) {
593                     rcserror("revision %s locked by %s; use co -r or rcs -u",
594 			num, next->login
595 		    );
596                     return -1;
597                 }
598                 trail=next;
599                 next=next->nextlock;
600         }
601 	if (next) {
602                 /*found one; delete it */
603                 trail->nextlock=next->nextlock;
604                 Locks=dummy.nextlock;
605 		next->delta->lockedby = 0;
606                 return 1; /*success*/
607         } else  return 0; /*no lock on delta*/
608 }
609 
610 
611 
612 
613 /*****************************************************************
614  * The rest of the routines are for handling joins
615  *****************************************************************/
616 
617 
618 	static char *
619 addjoin(joinrev)
620 	char *joinrev;
621 /* Add joinrev's number to joinlist, yielding address of char past joinrev,
622  * or 0 if no such revision exists.
623  */
624 {
625 	register char *j;
626 	register struct hshentry *d;
627 	char terminator;
628 	struct buf numrev;
629 	struct hshentries *joindeltas;
630 
631 	j = joinrev;
632 	for (;;) {
633 	    switch (*j++) {
634 		default:
635 		    continue;
636 		case 0:
637 		case ' ': case '\t': case '\n':
638 		case ':': case ',': case ';':
639 		    break;
640 	    }
641 	    break;
642 	}
643 	terminator = *--j;
644 	*j = 0;
645 	bufautobegin(&numrev);
646 	d = 0;
647 	if (expandsym(joinrev, &numrev))
648 	    d = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&joindeltas);
649 	bufautoend(&numrev);
650 	*j = terminator;
651 	if (d) {
652 		joinlist[++lastjoin] = d->num;
653 		return j;
654 	}
655 	return 0;
656 }
657 
658 	static int
659 preparejoin(j)
660 	register char *j;
661 /* Parse join list J and place pointers to the
662  * revision numbers into joinlist.
663  */
664 {
665         lastjoin= -1;
666         for (;;) {
667                 while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
668                 if (*j=='\0') break;
669                 if (lastjoin>=joinlength-2) {
670 		    joinlist =
671 			(joinlength *= 2) == 0
672 			? tnalloc(char const *, joinlength = 16)
673 			: trealloc(char const *, joinlist, joinlength);
674                 }
675 		if (!(j = addjoin(j))) return false;
676                 while ((*j==' ') || (*j=='\t')) j++;
677                 if (*j == ':') {
678                         j++;
679                         while((*j==' ') || (*j=='\t')) j++;
680                         if (*j!='\0') {
681 				if (!(j = addjoin(j))) return false;
682                         } else {
683 				rcsfaterror("join pair incomplete");
684                         }
685                 } else {
686                         if (lastjoin==0) { /* first pair */
687                                 /* common ancestor missing */
688                                 joinlist[1]=joinlist[0];
689                                 lastjoin=1;
690                                 /*derive common ancestor*/
691 				if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1])))
692                                        return false;
693                         } else {
694 				rcsfaterror("join pair incomplete");
695                         }
696                 }
697         }
698 	if (lastjoin < 1)
699 		rcsfaterror("empty join");
700 	return true;
701 }
702 
703 
704 
705 	static char const *
706 getancestor(r1, r2)
707 	char const *r1, *r2;
708 /* Yield the common ancestor of r1 and r2 if successful, 0 otherwise.
709  * Work reliably only if r1 and r2 are not branch numbers.
710  */
711 {
712 	static struct buf t1, t2;
713 
714 	int l1, l2, l3;
715 	char const *r;
716 
717 	l1 = countnumflds(r1);
718 	l2 = countnumflds(r2);
719 	if ((2<l1 || 2<l2)  &&  cmpnum(r1,r2)!=0) {
720 	    /* not on main trunk or identical */
721 	    l3 = 0;
722 	    while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
723 		l3 += 2;
724 	    /* This will terminate since r1 and r2 are not the same; see above. */
725 	    if (l3==0) {
726 		/* no common prefix; common ancestor on main trunk */
727 		VOID partialno(&t1, r1, l1>2 ? 2 : l1);
728 		VOID partialno(&t2, r2, l2>2 ? 2 : l2);
729 		r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string;
730 		if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0)
731 			return r;
732 	    } else if (cmpnumfld(r1, r2, l3+1)!=0)
733 			return partialno(&t1,r1,l3);
734 	}
735 	rcserror("common ancestor of %s and %s undefined", r1, r2);
736 	return 0;
737 }
738 
739 
740 
741 	static int
742 buildjoin(initialfile)
743 	char const *initialfile;
744 /* Function: merge pairs of elements in joinlist into initialfile
745  * If workstdout is set, copy result to stdout.
746  * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink().
747  */
748 {
749 	struct buf commarg;
750 	struct buf subs;
751 	char const *rev2, *rev3;
752         int i;
753 	char const *cov[10], *mergev[11];
754 	char const **p;
755 
756 	bufautobegin(&commarg);
757 	bufautobegin(&subs);
758 	rev2 = maketemp(0);
759 	rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
760 
761 	cov[1] = CO;
762 	/* cov[2] setup below */
763 	p = &cov[3];
764 	if (expandarg) *p++ = expandarg;
765 	if (suffixarg) *p++ = suffixarg;
766 	if (versionarg) *p++ = versionarg;
767 	if (zonearg) *p++ = zonearg;
768 	*p++ = quietarg;
769 	*p++ = RCSname;
770 	*p = 0;
771 
772 	mergev[1] = MERGE;
773 	mergev[2] = mergev[4] = "-L";
774 	/* rest of mergev setup below */
775 
776         i=0;
777         while (i<lastjoin) {
778                 /*prepare marker for merge*/
779                 if (i==0)
780 			bufscpy(&subs, targetdelta->num);
781 		else {
782 			bufscat(&subs, ",");
783 			bufscat(&subs, joinlist[i-2]);
784 			bufscat(&subs, ":");
785 			bufscat(&subs, joinlist[i-1]);
786 		}
787 		diagnose("revision %s\n",joinlist[i]);
788 		bufscpy(&commarg, "-p");
789 		bufscat(&commarg, joinlist[i]);
790 		cov[2] = commarg.string;
791 		if (runv(-1, rev2, cov))
792 			goto badmerge;
793 		diagnose("revision %s\n",joinlist[i+1]);
794 		bufscpy(&commarg, "-p");
795 		bufscat(&commarg, joinlist[i+1]);
796 		cov[2] = commarg.string;
797 		if (runv(-1, rev3, cov))
798 			goto badmerge;
799 		diagnose("merging...\n");
800 		mergev[3] = subs.string;
801 		mergev[5] = joinlist[i+1];
802 		p = &mergev[6];
803 		if (quietflag) *p++ = quietarg;
804 		if (lastjoin<=i+2 && workstdout) *p++ = "-p";
805 		*p++ = initialfile;
806 		*p++ = rev2;
807 		*p++ = rev3;
808 		*p = 0;
809 		switch (runv(-1, (char*)0, mergev)) {
810 		    case DIFF_FAILURE: case DIFF_SUCCESS:
811 			break;
812 		    default:
813 			goto badmerge;
814 		}
815                 i=i+2;
816         }
817 	bufautoend(&commarg);
818 	bufautoend(&subs);
819         return true;
820 
821     badmerge:
822 	nerror++;
823 	bufautoend(&commarg);
824 	bufautoend(&subs);
825 	return false;
826 }
827