1 /* $NetBSD: co.c,v 1.1.1.2 1996/10/13 21:56:49 veego Exp $ */
3 /* Check out working files from revisions of RCS files. */
5 /* Copyright 1982, 1988, 1989 Walter Tichy
6 Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
7 Distributed under license by the Free Software Foundation, Inc.
9 This file is part of RCS.
11 RCS is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2, or (at your option)
16 RCS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with RCS; see the file COPYING.
23 If not, write to the Free Software Foundation,
24 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 Report problems and direct all questions to:
28 rcs-bugs@cs.purdue.edu
34 * Revision 5.18 1995/06/16 06:19:24 eggert
37 * Revision 5.17 1995/06/01 16:23:43 eggert
38 * (main, preparejoin): Pass argument instead of using `join' static variable.
41 * Revision 5.16 1994/03/17 14:05:48 eggert
42 * Move buffer-flushes out of critical sections, since they aren't critical.
43 * Use ORCSerror to clean up after a fatal error. Remove lint.
44 * Specify subprocess input via file descriptor, not file name.
46 * Revision 5.15 1993/11/09 17:40:15 eggert
47 * -V now prints version on stdout and exits. Don't print usage twice.
49 * Revision 5.14 1993/11/03 17:42:27 eggert
50 * Add -z. Generate a value for the Name keyword.
51 * Don't arbitrarily limit the number of joins.
52 * Improve quality of diagnostics.
54 * Revision 5.13 1992/07/28 16:12:44 eggert
55 * Add -V. Check that working and RCS files are distinct.
57 * Revision 5.12 1992/02/17 23:02:08 eggert
60 * Revision 5.11 1992/01/24 18:44:19 eggert
61 * Add support for bad_creat0. lint -> RCS_lint
63 * Revision 5.10 1992/01/06 02:42:34 eggert
64 * Update usage string.
66 * Revision 5.9 1991/10/07 17:32:46 eggert
67 * -k affects just working file, not RCS file.
69 * Revision 5.8 1991/08/19 03:13:55 eggert
70 * Warn before removing somebody else's file.
71 * Add -M. Fix co -j bugs. Tune.
73 * Revision 5.7 1991/04/21 11:58:15 eggert
74 * Ensure that working file is newer than RCS file after co -[lu].
75 * Add -x, RCSINIT, MS-DOS support.
77 * Revision 5.6 1990/12/04 05:18:38 eggert
78 * Don't checkaccesslist() unless necessary.
79 * Use -I for prompts and -q for diagnostics.
81 * Revision 5.5 1990/11/01 05:03:26 eggert
84 * Revision 5.4 1990/10/04 06:30:11 eggert
85 * Accumulate exit status across files.
87 * Revision 5.3 1990/09/11 02:41:09 eggert
88 * co -kv yields a readonly working file.
90 * Revision 5.2 1990/09/04 08:02:13 eggert
91 * Standardize yes-or-no procedure.
93 * Revision 5.0 1990/08/22 08:10:02 eggert
94 * Permit multiple locks by same user. Add setuid support.
95 * Remove compile-time limits; use malloc instead.
96 * Permit dates past 1999/12/31. Switch to GMT.
97 * Make lock and temp files faster and safer.
98 * Ansify and Posixate. Add -k, -V. Remove snooping. Tune.
100 * Revision 4.7 89/05/01 15:11:41 narten
101 * changed copyright header to reflect current distribution rules
103 * Revision 4.6 88/08/09 19:12:15 eggert
104 * Fix "co -d" core dump; rawdate wasn't always initialized.
105 * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint
107 * Revision 4.5 87/12/18 11:35:40 narten
108 * lint cleanups (from Guy Harris)
110 * Revision 4.4 87/10/18 10:20:53 narten
111 * Updating version numbers changes relative to 1.1, are actually
114 * Revision 1.3 87/09/24 13:58:30 narten
115 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
118 * Revision 1.2 87/03/27 14:21:38 jenkins
121 * Revision 4.2 83/12/05 13:39:48 wft
122 * made rewriteflag external.
124 * Revision 4.1 83/05/10 16:52:55 wft
125 * Added option -u and -f.
126 * Added handling of default branch.
127 * Replaced getpwuid() with getcaller().
128 * Removed calls to stat(); now done by pairfilenames().
129 * Changed and renamed rmoldfile() to rmworkfile().
130 * Replaced catchints() calls with restoreints(), unlink()--link() with rename();
132 * Revision 3.7 83/02/15 15:27:07 wft
133 * Added call to fastcopy() to copy remainder of RCS file.
135 * Revision 3.6 83/01/15 14:37:50 wft
136 * Added ignoring of interrupts while RCS file is renamed; this avoids
137 * deletion of RCS files during the unlink/link window.
139 * Revision 3.5 82/12/08 21:40:11 wft
140 * changed processing of -d to use DATEFORM; removed actual from
141 * call to preparejoin; re-fixed printing of done at the end.
143 * Revision 3.4 82/12/04 18:40:00 wft
144 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
145 * Fixed printing of "done".
147 * Revision 3.3 82/11/28 22:23:11 wft
148 * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
149 * %02d with %.2d, mode generation for working file with WORKMODE.
150 * Fixed nil printing. Fixed -j combined with -l and -p, and exit
151 * for non-existing revisions in preparejoin().
153 * Revision 3.2 82/10/18 20:47:21 wft
154 * Mode of working file is now maintained even for co -l, but write permission
156 * The working file inherits its mode from the RCS file, plus write permission
157 * for the owner. The write permission is not given if locking is strict and
159 * An existing working file without write permission is deleted automatically.
160 * Otherwise, co asks (empty answer: abort co).
161 * Call to getfullRCSname() added, check for write error added, call
162 * for getlogin() fixed.
164 * Revision 3.1 82/10/13 16:01:30 wft
165 * fixed type of variables receiving from getc() (char -> int).
166 * removed unused variables.
174 static char *addjoin
P((char*));
175 static char const *getancestor
P((char const*,char const*));
176 static int buildjoin
P((char const*));
177 static int preparejoin
P((char*));
178 static int rmlock
P((struct hshentry
const*));
179 static int rmworkfile
P((void));
180 static void cleanup
P((void));
182 static char const quietarg
[] = "-q";
184 static char const *expandarg
, *suffixarg
, *versionarg
, *zonearg
;
185 static char const **joinlist
; /* revisions to be joined */
186 static int joinlength
;
187 static FILE *neworkptr
;
188 static int exitstatus
;
189 static int forceflag
;
190 static int lastjoin
; /* index of last element in joinlist */
191 static int lockflag
; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */
192 static int mtimeflag
;
193 static struct hshentries
*gendeltas
; /* deltas to be generated */
194 static struct hshentry
*targetdelta
; /* final delta to be generated */
195 static struct stat workstat
;
197 mainProg(coId
, "co", "Id: co.c,v 5.18 1995/06/16 06:19:24 eggert Exp")
199 static char const cmdusage
[] =
200 "\nco usage: co -{fIlMpqru}[rev] -ddate -jjoins -ksubst -sstate -T -w[who] -Vn -xsuff -zzone file ...";
202 char *a
, *joinflag
, **newargv
;
203 char const *author
, *date
, *rev
, *state
;
204 char const *joinname
, *newdate
, *neworkname
;
205 int changelock
; /* 1 if a lock has been changed, -1 if error */
206 int expmode
, r
, tostdout
, workstatstat
;
208 struct buf numericrev
; /* expanded revision number */
209 char finaldate
[datesize
];
215 author
= date
= rev
= state
= 0;
217 bufautobegin(&numericrev
);
219 suffixes
= X_DEFAULT
;
223 argc
= getRCSINIT(argc
, argv
, &newargv
);
225 while (a
= *++argv
, 0<--argc
&& *a
++=='-') {
231 if (rev
) warn("redefinition of revision number");
242 warn("-u overridden by -l.");
249 warn("-l overridden by -u.");
259 interactiveflag
= true;
269 str2date(a
, finaldate
);
275 if (joinflag
) redefined('j');
286 if (state
) redefined('s');
298 if (author
) redefined('w');
302 author
= getcaller();
312 setRCSversion(versionarg
);
320 case 'k': /* set keyword expand mode */
322 if (0 <= expmode
) redefined('k');
323 if (0 <= (expmode
= str2expmode(a
)))
328 error("unknown option: %s%s", *argv
, cmdusage
);
331 } /* end of option processing */
333 /* Now handle all pathnames. */
334 if (nerror
) cleanup();
335 else if (argc
< 1) faterror("no input file%s", cmdusage
);
336 else for (; 0 < argc
; cleanup(), ++argv
, --argc
) {
339 if (pairnames(argc
, argv
, lockflag
?rcswriteopen
:rcsreadopen
, true, false) <= 0)
343 * RCSname contains the name of the RCS file, and finptr
344 * points at it. workname contains the name of the working file.
345 * Also, RCSstat has been set.
347 diagnose("%s --> %s\n", RCSname
, tostdout
?"standard output":workname
);
352 int newmode
= Expand
==BINARY_EXPAND
? OPEN_O_BINARY
: 0;
353 if (stdout_mode
!= newmode
) {
354 stdout_mode
= newmode
;
356 VOID
setmode(STDOUT_FILENO
, newmode
);
360 neworkptr
= workstdout
= stdout
;
362 workstatstat
= stat(workname
, &workstat
);
363 if (workstatstat
== 0 && same_file(RCSstat
, workstat
, 0)) {
364 rcserror("RCS file is the same as working file %s.",
369 neworkname
= makedirtemp(1);
370 if (!(neworkptr
= fopenSafer(neworkname
, FOPEN_W_WORK
))) {
372 workerror("permission denied on parent directory");
379 gettree(); /* reads in the delta tree */
382 /* no revisions; create empty file */
383 diagnose("no revisions present; generating empty revision 0.0\n");
386 "no revisions, so nothing can be %slocked",
387 lockflag
< 0 ? "un" : ""
390 if (workstatstat
== 0)
391 if (!rmworkfile()) continue;
395 int locks
= lockflag
? findlock(false, &targetdelta
) : 0;
397 /* expand symbolic revision number */
398 if (!expandsym(rev
, &numericrev
))
405 bufscpy(&numericrev
, Dbranch
?Dbranch
:"");
408 bufscpy(&numericrev
, targetdelta
->num
);
412 /* get numbers of deltas to be generated */
413 if (!(targetdelta
=genrevs(numericrev
.string
,date
,author
,state
,&gendeltas
)))
415 /* check reservations */
422 addlock(targetdelta
, true);
426 || (changelock
&& !checkaccesslist())
427 || dorewrite(lockflag
, changelock
) != 0
433 if (0 < lockflag
&& Expand
== VAL_EXPAND
) {
434 rcserror("cannot combine -kv and -l");
438 if (joinflag
&& !preparejoin(joinflag
))
441 diagnose("revision %s%s\n",targetdelta
->num
,
442 0<lockflag
? " (locked)" :
443 lockflag
<0 ? " (unlocked)" : "");
445 /* Prepare to remove old working file if necessary. */
446 if (workstatstat
== 0)
447 if (!rmworkfile()) continue;
449 /* skip description */
450 getdesc(false); /* don't echo*/
452 locker_expansion
= 0 < lockflag
;
453 targetdelta
->name
= namedrev(rev
, targetdelta
);
454 joinname
= buildrevision(
455 gendeltas
, targetdelta
,
456 joinflag
&&tostdout
? (FILE*)0 : neworkptr
,
457 Expand
< MIN_UNEXPAND
460 if (fcopy
== neworkptr
)
461 fcopy
= 0; /* Don't close it twice. */
463 if_advise_access(changelock
&& gendeltas
->first
!=targetdelta
,
464 finptr
, MADV_SEQUENTIAL
467 if (donerewrite(changelock
,
468 Ttimeflag
? RCSstat
.st_mtime
: (time_t)-1
475 rcswarn("You now have %d locks.", locks
);
478 newdate
= targetdelta
->date
;
483 joinname
= neworkname
;
485 if (Expand
== BINARY_EXPAND
)
486 workerror("merging binary files");
487 if (!buildjoin(joinname
))
492 mode_t m
= WORKMODE(RCSstat
.st_mode
,
493 ! (Expand
==VAL_EXPAND
|| (lockflag
<=0 && StrictLocks
))
495 time_t t
= mtimeflag
&&newdate
? date2time(newdate
) : (time_t)-1;
498 r
= chnamemod(&neworkptr
, neworkname
, workname
, 1, m
, t
);
499 keepdirtemp(neworkname
);
503 error("see %s", neworkname
);
512 exitmain(exitstatus
);
514 } /* end of main (co) */
519 if (nerror
) exitstatus
= EXIT_FAILURE
;
523 if (fcopy
!=workstdout
) Ozclose(&fcopy
);
525 if (neworkptr
!=workstdout
) Ozclose(&neworkptr
);
530 # define exiterr coExit
542 /*****************************************************************
543 * The following routines are auxiliary routines
544 *****************************************************************/
549 * Prepare to remove workname, if it exists, and if
551 * Otherwise (file writable):
552 * if !quietmode asks the user whether to really delete it (default: fail);
554 * Returns true if permission is gotten.
557 if (workstat
.st_mode
&(S_IWUSR
|S_IWGRP
|S_IWOTH
) && !forceflag
) {
558 /* File is writable */
559 if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ",
561 myself(workstat
.st_uid
) ? "" : ", and you do not own it"
563 error(!quietflag
&& ttystdin()
565 : "writable %s exists; checkout aborted", workname
);
569 /* Actual unlink is done later by caller. */
576 struct hshentry
const *delta
;
577 /* Function: removes the lock held by caller on delta.
578 * Returns -1 if someone else holds the lock,
579 * 0 if there is no lock on delta,
580 * and 1 if a lock was found and removed.
582 { register struct rcslock
* next
, * trail
;
584 struct rcslock dummy
;
585 int whomatch
, nummatch
;
588 dummy
.nextlock
=next
=Locks
;
591 whomatch
= strcmp(getcaller(), next
->login
);
592 nummatch
=strcmp(num
,next
->delta
->num
);
593 if ((whomatch
==0) && (nummatch
==0)) break;
594 /*found a lock on delta by caller*/
595 if ((whomatch
!=0)&&(nummatch
==0)) {
596 rcserror("revision %s locked by %s; use co -r or rcs -u",
605 /*found one; delete it */
606 trail
->nextlock
=next
->nextlock
;
607 Locks
=dummy
.nextlock
;
608 next
->delta
->lockedby
= 0;
609 return 1; /*success*/
610 } else return 0; /*no lock on delta*/
616 /*****************************************************************
617 * The rest of the routines are for handling joins
618 *****************************************************************/
624 /* Add joinrev's number to joinlist, yielding address of char past joinrev,
625 * or 0 if no such revision exists.
629 register struct hshentry
*d
;
632 struct hshentries
*joindeltas
;
640 case ' ': case '\t': case '\n':
641 case ':': case ',': case ';':
648 bufautobegin(&numrev
);
650 if (expandsym(joinrev
, &numrev
))
651 d
= genrevs(numrev
.string
,(char*)0,(char*)0,(char*)0,&joindeltas
);
655 joinlist
[++lastjoin
] = d
->num
;
664 /* Parse join list J and place pointers to the
665 * revision numbers into joinlist.
670 while ((*j
==' ')||(*j
=='\t')||(*j
==',')) j
++;
672 if (lastjoin
>=joinlength
-2) {
674 (joinlength
*= 2) == 0
675 ? tnalloc(char const *, joinlength
= 16)
676 : trealloc(char const *, joinlist
, joinlength
);
678 if (!(j
= addjoin(j
))) return false;
679 while ((*j
==' ') || (*j
=='\t')) j
++;
682 while((*j
==' ') || (*j
=='\t')) j
++;
684 if (!(j
= addjoin(j
))) return false;
686 rcsfaterror("join pair incomplete");
689 if (lastjoin
==0) { /* first pair */
690 /* common ancestor missing */
691 joinlist
[1]=joinlist
[0];
693 /*derive common ancestor*/
694 if (!(joinlist
[0] = getancestor(targetdelta
->num
,joinlist
[1])))
697 rcsfaterror("join pair incomplete");
702 rcsfaterror("empty join");
711 /* Yield the common ancestor of r1 and r2 if successful, 0 otherwise.
712 * Work reliably only if r1 and r2 are not branch numbers.
715 static struct buf t1
, t2
;
720 l1
= countnumflds(r1
);
721 l2
= countnumflds(r2
);
722 if ((2<l1
|| 2<l2
) && cmpnum(r1
,r2
)!=0) {
723 /* not on main trunk or identical */
725 while (cmpnumfld(r1
, r2
, l3
+1)==0 && cmpnumfld(r1
, r2
, l3
+2)==0)
727 /* This will terminate since r1 and r2 are not the same; see above. */
729 /* no common prefix; common ancestor on main trunk */
730 VOID
partialno(&t1
, r1
, l1
>2 ? 2 : l1
);
731 VOID
partialno(&t2
, r2
, l2
>2 ? 2 : l2
);
732 r
= cmpnum(t1
.string
,t2
.string
)<0 ? t1
.string
: t2
.string
;
733 if (cmpnum(r
,r1
)!=0 && cmpnum(r
,r2
)!=0)
735 } else if (cmpnumfld(r1
, r2
, l3
+1)!=0)
736 return partialno(&t1
,r1
,l3
);
738 rcserror("common ancestor of %s and %s undefined", r1
, r2
);
745 buildjoin(initialfile
)
746 char const *initialfile
;
747 /* Function: merge pairs of elements in joinlist into initialfile
748 * If workstdout is set, copy result to stdout.
749 * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink().
754 char const *rev2
, *rev3
;
756 char const *cov
[10], *mergev
[11];
759 bufautobegin(&commarg
);
762 rev3
= maketemp(3); /* buildrevision() may use 1 and 2 */
765 /* cov[2] setup below */
767 if (expandarg
) *p
++ = expandarg
;
768 if (suffixarg
) *p
++ = suffixarg
;
769 if (versionarg
) *p
++ = versionarg
;
770 if (zonearg
) *p
++ = zonearg
;
776 mergev
[2] = mergev
[4] = "-L";
777 /* rest of mergev setup below */
781 /*prepare marker for merge*/
783 bufscpy(&subs
, targetdelta
->num
);
786 bufscat(&subs
, joinlist
[i
-2]);
788 bufscat(&subs
, joinlist
[i
-1]);
790 diagnose("revision %s\n",joinlist
[i
]);
791 bufscpy(&commarg
, "-p");
792 bufscat(&commarg
, joinlist
[i
]);
793 cov
[2] = commarg
.string
;
794 if (runv(-1, rev2
, cov
))
796 diagnose("revision %s\n",joinlist
[i
+1]);
797 bufscpy(&commarg
, "-p");
798 bufscat(&commarg
, joinlist
[i
+1]);
799 cov
[2] = commarg
.string
;
800 if (runv(-1, rev3
, cov
))
802 diagnose("merging...\n");
803 mergev
[3] = subs
.string
;
804 mergev
[5] = joinlist
[i
+1];
806 if (quietflag
) *p
++ = quietarg
;
807 if (lastjoin
<=i
+2 && workstdout
) *p
++ = "-p";
812 switch (runv(-1, (char*)0, mergev
)) {
813 case DIFF_FAILURE
: case DIFF_SUCCESS
:
820 bufautoend(&commarg
);
826 bufautoend(&commarg
);