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