1 /* Check out working files from revisions of RCS files. */
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.
7 This file is part of RCS.
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)
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.
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.
24 Report problems and direct all questions to:
26 rcs-bugs@cs.purdue.edu
31 * Revision 5.18 1995/06/16 06:19:24 eggert
34 * Revision 5.17 1995/06/01 16:23:43 eggert
35 * (main, preparejoin): Pass argument instead of using `join' static variable.
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.
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.
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.
51 * Revision 5.13 1992/07/28 16:12:44 eggert
52 * Add -V. Check that working and RCS files are distinct.
54 * Revision 5.12 1992/02/17 23:02:08 eggert
57 * Revision 5.11 1992/01/24 18:44:19 eggert
58 * Add support for bad_creat0. lint -> RCS_lint
60 * Revision 5.10 1992/01/06 02:42:34 eggert
61 * Update usage string.
63 * Revision 5.9 1991/10/07 17:32:46 eggert
64 * -k affects just working file, not RCS file.
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.
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.
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.
78 * Revision 5.5 1990/11/01 05:03:26 eggert
81 * Revision 5.4 1990/10/04 06:30:11 eggert
82 * Accumulate exit status across files.
84 * Revision 5.3 1990/09/11 02:41:09 eggert
85 * co -kv yields a readonly working file.
87 * Revision 5.2 1990/09/04 08:02:13 eggert
88 * Standardize yes-or-no procedure.
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.
97 * Revision 4.7 89/05/01 15:11:41 narten
98 * changed copyright header to reflect current distribution rules
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
104 * Revision 4.5 87/12/18 11:35:40 narten
105 * lint cleanups (from Guy Harris)
107 * Revision 4.4 87/10/18 10:20:53 narten
108 * Updating version numbers changes relative to 1.1, are actually
111 * Revision 1.3 87/09/24 13:58:30 narten
112 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
115 * Revision 1.2 87/03/27 14:21:38 jenkins
118 * Revision 4.2 83/12/05 13:39:48 wft
119 * made rewriteflag external.
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();
129 * Revision 3.7 83/02/15 15:27:07 wft
130 * Added call to fastcopy() to copy remainder of RCS file.
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.
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.
140 * Revision 3.4 82/12/04 18:40:00 wft
141 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
142 * Fixed printing of "done".
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().
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
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
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.
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.
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));
179 static char const quietarg[] = "-q";
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;
194 mainProg(coId, "co", "$FreeBSD$")
196 static char const cmdusage[] =
197 "\nco usage: co -{fIlMpqru}[rev] -ddate -jjoins -ksubst -sstate -T -w[who] -Vn -xsuff -zzone file ...";
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;
205 struct buf numericrev; /* expanded revision number */
206 char finaldate[datesize];
212 author = date = rev = state = 0;
214 bufautobegin(&numericrev);
216 suffixes = X_DEFAULT;
220 argc = getRCSINIT(argc, argv, &newargv);
222 while (a = *++argv, 0<--argc && *a++=='-') {
228 if (rev) warn("redefinition of revision number");
239 warn("-u overridden by -l.");
246 warn("-l overridden by -u.");
256 interactiveflag = true;
266 str2date(a, finaldate);
272 if (joinflag) redefined('j');
283 if (state) redefined('s');
295 if (author) redefined('w');
299 author = getcaller();
309 setRCSversion(versionarg);
317 case 'k': /* set keyword expand mode */
319 if (0 <= expmode) redefined('k');
320 if (0 <= (expmode = str2expmode(a)))
325 error("unknown option: %s%s", *argv, cmdusage);
328 } /* end of option processing */
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) {
336 if (pairnames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false) <= 0)
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.
344 diagnose("%s --> %s\n", RCSname, tostdout?"standard output":workname);
349 int newmode = Expand==BINARY_EXPAND ? OPEN_O_BINARY : 0;
350 if (stdout_mode != newmode) {
351 stdout_mode = newmode;
353 VOID setmode(STDOUT_FILENO, newmode);
357 neworkptr = workstdout = stdout;
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.",
366 neworkname = makedirtemp(1);
367 if (!(neworkptr = fopenSafer(neworkname, FOPEN_W_WORK))) {
369 workerror("permission denied on parent directory");
376 gettree(); /* reads in the delta tree */
379 /* no revisions; create empty file */
380 diagnose("no revisions present; generating empty revision 0.0\n");
383 "no revisions, so nothing can be %slocked",
384 lockflag < 0 ? "un" : ""
387 if (workstatstat == 0)
388 if (!rmworkfile()) continue;
392 int locks = lockflag ? findlock(false, &targetdelta) : 0;
394 /* expand symbolic revision number */
395 if (!expandsym(rev, &numericrev))
402 bufscpy(&numericrev, Dbranch?Dbranch:"");
405 bufscpy(&numericrev, targetdelta->num);
409 /* get numbers of deltas to be generated */
410 if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas)))
412 /* check reservations */
419 addlock(targetdelta, true);
423 || (changelock && !checkaccesslist())
424 || dorewrite(lockflag, changelock) != 0
430 if (0 < lockflag && Expand == VAL_EXPAND) {
431 rcserror("cannot combine -kv and -l");
435 if (joinflag && !preparejoin(joinflag))
438 diagnose("revision %s%s\n",targetdelta->num,
439 0<lockflag ? " (locked)" :
440 lockflag<0 ? " (unlocked)" : "");
442 /* Prepare to remove old working file if necessary. */
443 if (workstatstat == 0)
444 if (!rmworkfile()) continue;
446 /* skip description */
447 getdesc(false); /* don't echo*/
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
457 if (fcopy == neworkptr)
458 fcopy = 0; /* Don't close it twice. */
460 if_advise_access(changelock && gendeltas->first!=targetdelta,
461 finptr, MADV_SEQUENTIAL
464 if (donerewrite(changelock,
465 Ttimeflag ? RCSstat.st_mtime : (time_t)-1
472 rcswarn("You now have %d locks.", locks);
475 newdate = targetdelta->date;
480 joinname = neworkname;
482 if (Expand == BINARY_EXPAND)
483 workerror("merging binary files");
484 if (!buildjoin(joinname))
489 mode_t m = WORKMODE(RCSstat.st_mode,
490 ! (Expand==VAL_EXPAND || (lockflag<=0 && StrictLocks))
492 time_t t = mtimeflag&&newdate ? date2time(newdate) : (time_t)-1;
495 r = chnamemod(&neworkptr, neworkname, workname, 1, m, t);
496 keepdirtemp(neworkname);
500 error("see %s", neworkname);
509 exitmain(exitstatus);
511 } /* end of main (co) */
516 if (nerror) exitstatus = EXIT_FAILURE;
520 if (fcopy!=workstdout) Ozclose(&fcopy);
522 if (neworkptr!=workstdout) Ozclose(&neworkptr);
527 # define exiterr coExit
539 /*****************************************************************
540 * The following routines are auxiliary routines
541 *****************************************************************/
546 * Prepare to remove workname, if it exists, and if
548 * Otherwise (file writable):
549 * if !quietmode asks the user whether to really delete it (default: fail);
551 * Returns true if permission is gotten.
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): ",
558 myself(workstat.st_uid) ? "" : ", and you do not own it"
560 error(!quietflag && ttystdin()
562 : "writable %s exists; checkout aborted", workname);
566 /* Actual unlink is done later by caller. */
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.
579 { register struct rcslock * next, * trail;
581 struct rcslock dummy;
582 int whomatch, nummatch;
585 dummy.nextlock=next=Locks;
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",
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*/
613 /*****************************************************************
614 * The rest of the routines are for handling joins
615 *****************************************************************/
621 /* Add joinrev's number to joinlist, yielding address of char past joinrev,
622 * or 0 if no such revision exists.
626 register struct hshentry *d;
629 struct hshentries *joindeltas;
637 case ' ': case '\t': case '\n':
638 case ':': case ',': case ';':
645 bufautobegin(&numrev);
647 if (expandsym(joinrev, &numrev))
648 d = genrevs(numrev.string,(char*)0,(char*)0,(char*)0,&joindeltas);
652 joinlist[++lastjoin] = d->num;
661 /* Parse join list J and place pointers to the
662 * revision numbers into joinlist.
667 while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
669 if (lastjoin>=joinlength-2) {
671 (joinlength *= 2) == 0
672 ? tnalloc(char const *, joinlength = 16)
673 : trealloc(char const *, joinlist, joinlength);
675 if (!(j = addjoin(j))) return false;
676 while ((*j==' ') || (*j=='\t')) j++;
679 while((*j==' ') || (*j=='\t')) j++;
681 if (!(j = addjoin(j))) return false;
683 rcsfaterror("join pair incomplete");
686 if (lastjoin==0) { /* first pair */
687 /* common ancestor missing */
688 joinlist[1]=joinlist[0];
690 /*derive common ancestor*/
691 if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1])))
694 rcsfaterror("join pair incomplete");
699 rcsfaterror("empty join");
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.
712 static struct buf t1, t2;
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 */
722 while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
724 /* This will terminate since r1 and r2 are not the same; see above. */
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)
732 } else if (cmpnumfld(r1, r2, l3+1)!=0)
733 return partialno(&t1,r1,l3);
735 rcserror("common ancestor of %s and %s undefined", r1, r2);
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().
751 char const *rev2, *rev3;
753 char const *cov[10], *mergev[11];
756 bufautobegin(&commarg);
759 rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
762 /* cov[2] setup below */
764 if (expandarg) *p++ = expandarg;
765 if (suffixarg) *p++ = suffixarg;
766 if (versionarg) *p++ = versionarg;
767 if (zonearg) *p++ = zonearg;
773 mergev[2] = mergev[4] = "-L";
774 /* rest of mergev setup below */
778 /*prepare marker for merge*/
780 bufscpy(&subs, targetdelta->num);
783 bufscat(&subs, joinlist[i-2]);
785 bufscat(&subs, joinlist[i-1]);
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))
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))
799 diagnose("merging...\n");
800 mergev[3] = subs.string;
801 mergev[5] = joinlist[i+1];
803 if (quietflag) *p++ = quietarg;
804 if (lastjoin<=i+2 && workstdout) *p++ = "-p";
809 switch (runv(-1, (char*)0, mergev)) {
810 case DIFF_FAILURE: case DIFF_SUCCESS:
817 bufautoend(&commarg);
823 bufautoend(&commarg);