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