1 /* $NetBSD: ci.c,v 1.1.1.2 1996/10/13 21:56:43 veego Exp $ */
3 /* Check in revisions of RCS files from working 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.30 1995/06/16 06:19:24 eggert
37 * Revision 5.29 1995/06/01 16:23:43 eggert
39 * Use `cmpdate', not `cmpnum', to compare dates.
40 * This is for MKS RCS's incompatible 20th-century date format.
41 * Don't worry about errno after ftruncate fails.
42 * Fix input file rewinding bug when large_memory && !maps_memory
43 * and checking in a branch tip.
45 * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS.
47 * Revision 5.28 1994/03/20 04:52:58 eggert
48 * Do not generate a corrupted RCS file if the user modifies the working file
49 * while `ci' is running.
50 * Do not remove the lock when `ci -l' reverts.
51 * Move buffer-flushes out of critical sections, since they aren't critical.
52 * Use ORCSerror to clean up after a fatal error.
53 * Specify subprocess input via file descriptor, not file name.
55 * Revision 5.27 1993/11/09 17:40:15 eggert
56 * -V now prints version on stdout and exits. Don't print usage twice.
58 * Revision 5.26 1993/11/03 17:42:27 eggert
59 * Add -z. Don't subtract from RCS file timestamp even if -T.
60 * Scan for and use Name keyword if -k.
61 * Don't discard ignored phrases. Improve quality of diagnostics.
63 * Revision 5.25 1992/07/28 16:12:44 eggert
64 * Add -i, -j, -V. Check that working and RCS files are distinct.
66 * Revision 5.24 1992/02/17 23:02:06 eggert
67 * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default.
70 * Revision 5.23 1992/01/27 16:42:51 eggert
71 * Always unlock branchpoint if caller has a lock.
72 * Add support for bad_chmod_close, bad_creat0. lint -> RCS_lint
74 * Revision 5.22 1992/01/06 02:42:34 eggert
75 * Invoke utime() before chmod() to keep some buggy systems happy.
77 * Revision 5.21 1991/11/20 17:58:07 eggert
78 * Don't read the delta tree from a nonexistent RCS file.
80 * Revision 5.20 1991/10/07 17:32:46 eggert
81 * Fix log bugs. Remove lint.
83 * Revision 5.19 1991/09/26 23:10:30 eggert
84 * Plug file descriptor leak.
86 * Revision 5.18 1991/09/18 07:29:10 eggert
87 * Work around a common ftruncate() bug.
89 * Revision 5.17 1991/09/10 22:15:46 eggert
90 * Fix test for redirected stdin.
92 * Revision 5.16 1991/08/19 23:17:54 eggert
93 * When there are no changes, revert to previous revision instead of aborting.
94 * Add piece tables, -M, -r$. Tune.
96 * Revision 5.15 1991/04/21 11:58:14 eggert
97 * Ensure that working file is newer than RCS file after ci -[lu].
98 * Add -x, RCSINIT, MS-DOS support.
100 * Revision 5.14 1991/02/28 19:18:47 eggert
101 * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first.
102 * Fix ci -ko -l mode bug. Open work file at most once.
104 * Revision 5.13 1991/02/25 07:12:33 eggert
105 * getdate -> getcurdate (SVR4 name clash)
107 * Revision 5.12 1990/12/31 01:00:12 eggert
108 * Don't use uninitialized storage when handling -{N,n}.
110 * Revision 5.11 1990/12/04 05:18:36 eggert
111 * Use -I for prompts and -q for diagnostics.
113 * Revision 5.10 1990/11/05 20:30:10 eggert
114 * Don't remove working file when aborting due to no changes.
116 * Revision 5.9 1990/11/01 05:03:23 eggert
117 * Add -I and new -t behavior. Permit arbitrary data in logs.
119 * Revision 5.8 1990/10/04 06:30:09 eggert
120 * Accumulate exit status across files.
122 * Revision 5.7 1990/09/25 20:11:46 hammer
123 * fixed another small typo
125 * Revision 5.6 1990/09/24 21:48:50 hammer
126 * added cleanups from Paul Eggert.
128 * Revision 5.5 1990/09/21 06:16:38 hammer
129 * made it handle multiple -{N,n}'s. Also, made it treat re-directed stdin
130 * the same as the terminal
132 * Revision 5.4 1990/09/20 02:38:51 eggert
133 * ci -k now checks dates more thoroughly.
135 * Revision 5.3 1990/09/11 02:41:07 eggert
136 * Fix revision bug with `ci -k file1 file2'.
138 * Revision 5.2 1990/09/04 08:02:10 eggert
139 * Permit adjacent revisions with identical time stamps (possible on fast hosts).
140 * Improve incomplete line handling. Standardize yes-or-no procedure.
142 * Revision 5.1 1990/08/29 07:13:44 eggert
143 * Expand locker value like co. Clean old log messages too.
145 * Revision 5.0 1990/08/22 08:10:00 eggert
146 * Don't require a final newline.
147 * Make lock and temp files faster and safer.
148 * Remove compile-time limits; use malloc instead.
149 * Permit dates past 1999/12/31. Switch to GMT.
150 * Add setuid support. Don't pass +args to diff. Check diff's output.
151 * Ansify and Posixate. Add -k, -V. Remove snooping. Tune.
152 * Check diff's output.
154 * Revision 4.9 89/05/01 15:10:54 narten
155 * changed copyright header to reflect current distribution rules
157 * Revision 4.8 88/11/08 13:38:23 narten
158 * changes from root@seismo.CSS.GOV (Super User)
159 * -d with no arguments uses the mod time of the file it is checking in
161 * Revision 4.7 88/08/09 19:12:07 eggert
162 * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
163 * Use execv(), not system(); allow cc -R; remove lint.
164 * isatty(fileno(stdin)) -> ttystdin()
166 * Revision 4.6 87/12/18 11:34:41 narten
167 * lint cleanups (from Guy Harris)
169 * Revision 4.5 87/10/18 10:18:48 narten
170 * Updating version numbers. Changes relative to revision 1.1 are actually
173 * Revision 1.3 87/09/24 13:57:19 narten
174 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
177 * Revision 1.2 87/03/27 14:21:33 jenkins
180 * Revision 4.3 83/12/15 12:28:54 wft
181 * ci -u and ci -l now set mode of working file properly.
183 * Revision 4.2 83/12/05 13:40:54 wft
184 * Merged with 3.9.1.1: added calls to clearerr(stdin).
185 * made rewriteflag external.
187 * Revision 4.1 83/05/10 17:03:06 wft
188 * Added option -d and -w, and updated assingment of date, etc. to new delta.
189 * Added handling of default branches.
190 * Option -k generates std. log message; fixed undef. pointer in reading of log.
191 * Replaced getlock() with findlock(), link--unlink with rename(),
192 * getpwuid() with getcaller().
193 * Moved all revision number generation to new routine addelta().
194 * Removed calls to stat(); now done by pairfilenames().
195 * Changed most calls to catchints() with restoreints().
196 * Directed all interactive messages to stderr.
198 * Revision 3.9.1.1 83/10/19 04:21:03 lepreau
199 * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
201 * Revision 3.9 83/02/15 15:25:44 wft
204 * Revision 3.9 83/02/15 15:25:44 wft
205 * Added call to fastcopy() to copy remainder of RCS file.
207 * Revision 3.8 83/01/14 15:34:05 wft
208 * Added ignoring of interrupts while new RCS file is renamed;
209 * Avoids deletion of RCS files by interrupts.
211 * Revision 3.7 82/12/10 16:09:20 wft
212 * Corrected checking of return code from diff.
214 * Revision 3.6 82/12/08 21:34:49 wft
215 * Using DATEFORM to prepare date of checked-in revision;
216 * Fixed return from addbranch().
218 * Revision 3.5 82/12/04 18:32:42 wft
219 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
220 * field lockedby in removelock(), moved getlogmsg() before calling diff.
222 * Revision 3.4 82/12/02 13:27:13 wft
225 * Revision 3.3 82/11/28 20:53:31 wft
226 * Added mustcheckin() to check for redundant checkins.
227 * Added xpandfile() to do keyword expansion for -u and -l;
228 * -m appends linefeed to log message if necessary.
229 * getlogmsg() suppresses prompt if stdin is not a terminal.
230 * Replaced keeplock with lockflag, fclose() with ffclose(),
231 * %02d with %.2d, getlogin() with getpwuid().
233 * Revision 3.2 82/10/18 20:57:23 wft
234 * An RCS file inherits its mode during the first ci from the working file,
235 * otherwise it stays the same, except that write permission is removed.
236 * Fixed ci -l, added ci -u (both do an implicit co after the ci).
237 * Fixed call to getlogin(), added call to getfullRCSname(), added check
239 * Changed conflicting identifiers.
241 * Revision 3.1 82/10/13 16:04:59 wft
242 * fixed type of variables receiving from getc() (char -> int).
243 * added include file dbm.h for getting BYTESIZ. This is used
244 * to check the return code from diff portably.
252 struct Symrev
* nextsym
;
255 static char const *getcurdate
P((void));
256 static int addbranch
P((struct hshentry
*,struct buf
*,int));
257 static int addelta
P((void));
258 static int addsyms
P((char const*));
259 static int fixwork
P((mode_t
,time_t));
260 static int removelock
P((struct hshentry
*));
261 static int xpandfile
P((RILE
*,struct hshentry
const*,char const**,int));
262 static struct cbuf getlogmsg
P((void));
263 static void cleanup
P((void));
264 static void incnum
P((char const*,struct buf
*));
265 static void addassoclst
P((int,char const*));
268 static RILE
*workptr
; /* working file pointer */
269 static struct buf newdelnum
; /* new revision number */
270 static struct cbuf msg
;
271 static int exitstatus
;
272 static int forceciflag
; /* forces check in */
273 static int keepflag
, keepworkingfile
, rcsinitflag
;
274 static struct hshentries
*gendeltas
; /* deltas to be generated */
275 static struct hshentry
*targetdelta
; /* old delta to be generated */
276 static struct hshentry newdelta
; /* new delta to be inserted */
277 static struct stat workstat
;
278 static struct Symrev
*assoclst
, **nextassoc
;
280 mainProg(ciId
, "ci", "Id: ci.c,v 5.30 1995/06/16 06:19:24 eggert Exp")
282 static char const cmdusage
[] =
283 "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ...";
284 static char const default_state
[] = DEFAULTSTATE
;
286 char altdate
[datesize
];
287 char olddate
[datesize
];
288 char newdatebuf
[datesize
+ zonelenmax
];
289 char targetdatebuf
[datesize
+ zonelenmax
];
290 char *a
, **newargv
, *textfile
;
291 char const *author
, *krev
, *rev
, *state
;
292 char const *diffname
, *expname
;
293 char const *newworkname
;
294 int initflag
, mustread
;
295 int lockflag
, lockthis
, mtimeflag
, removedlock
, Ttimeflag
;
297 int changedRCS
, changework
, dolog
, newhead
;
298 int usestatdate
; /* Use mod time of file for -d. */
299 mode_t newworkmode
; /* mode for working file */
301 struct hshentry
*workdelta
;
305 author
= rev
= state
= textfile
= 0;
306 initflag
= lockflag
= mustread
= false;
309 altdate
[0]= '\0'; /* empty alternate date for -d */
311 suffixes
= X_DEFAULT
;
312 nextassoc
= &assoclst
;
314 argc
= getRCSINIT(argc
, argv
, &newargv
);
316 while (a
= *++argv
, 0<--argc
&& *a
++=='-') {
322 keepworkingfile
= lockflag
= false;
326 keepworkingfile
= lockflag
= true;
329 if (rev
) warn("redefinition of revision number");
335 keepworkingfile
=true; lockflag
=false;
347 interactiveflag
= true;
363 if (msg
.size
) redefined('m');
364 msg
= cleanlogmsg(a
, strlen(a
));
366 error("missing message for -m option");
371 error("missing symbolic name after -n");
375 addassoclst(false, a
);
380 error("missing symbolic name after -N");
384 addassoclst(true, a
);
389 if (state
) redefined('s');
393 error("missing state for -s option");
398 if (textfile
) redefined('t');
404 if (altdate
[0] || usestatdate
)
407 if (!(usestatdate
= !*a
))
408 str2date(a
, altdate
);
417 if (author
) redefined('w');
421 error("missing author for -w option");
429 setRCSversion(*argv
);
443 error("unknown option: %s%s", *argv
, cmdusage
);
445 } /* end processing of options */
447 /* Handle all pathnames. */
448 if (nerror
) cleanup();
449 else if (argc
< 1) faterror("no input file%s", cmdusage
);
450 else for (; 0 < argc
; cleanup(), ++argv
, --argc
) {
454 switch (pairnames(argc
, argv
, rcswriteopen
, mustread
, false)) {
456 case -1: /* New RCS file */
457 # if has_setuid && has_getuid
458 if (euid() != ruid()) {
459 workerror("setuid initial checkin prohibited; use `rcs -i -a' first");
469 case 1: /* Normal checkin with prev . RCS file */
471 rcserror("already exists");
478 * RCSname contains the name of the RCS file, and
479 * workname contains the name of the working file.
480 * If the RCS file exists, finptr contains the file descriptor for the
481 * RCS file, and RCSstat is set. The admin node is initialized.
484 diagnose("%s <-- %s\n", RCSname
, workname
);
486 if (!(workptr
= Iopen(workname
, FOPEN_R_WORK
, &workstat
))) {
492 if (same_file(RCSstat
, workstat
, 0)) {
493 rcserror("RCS file is the same as working file %s.",
498 if (!checkaccesslist())
504 /* get keyword values from working file */
505 if (!getoldkeys(workptr
)) continue;
506 if (!rev
&& !*(krev
= prevrev
.string
)) {
507 workerror("can't find a revision number");
510 if (!*prevdate
.string
&& *altdate
=='\0' && usestatdate
==false)
511 workwarn("can't find a date");
512 if (!*prevauthor
.string
&& !author
)
513 workwarn("can't find an author");
514 if (!*prevstate
.string
&& !state
)
515 workwarn("can't find a state");
516 } /* end processing keepflag */
518 /* Read the delta tree. */
522 /* expand symbolic revision number */
523 if (!fexpandsym(krev
, &newdelnum
, workptr
))
526 /* splice new delta into tree */
527 if ((removedlock
= addelta()) < 0)
530 newdelta
.num
= newdelnum
.string
;
531 newdelta
.branches
= 0;
532 newdelta
.lockedby
= 0; /* This might be changed by addlock(). */
533 newdelta
.selector
= true;
535 clear_buf(&newdelta
.ig
);
536 clear_buf(&newdelta
.igtext
);
539 newdelta
.author
=author
; /* set author given by -w */
540 else if (keepflag
&& *prevauthor
.string
)
541 newdelta
.author
=prevauthor
.string
; /* preserve old author if possible*/
542 else newdelta
.author
=getcaller();/* otherwise use caller's id */
543 newdelta
.state
= default_state
;
545 newdelta
.state
=state
; /* set state given by -s */
546 else if (keepflag
&& *prevstate
.string
)
547 newdelta
.state
=prevstate
.string
; /* preserve old state if possible */
549 time2date(workstat
.st_mtime
, altdate
);
552 newdelta
.date
=altdate
; /* set date given by -d */
553 else if (keepflag
&& *prevdate
.string
) {
554 /* Preserve old date if possible. */
555 str2date(prevdate
.string
, olddate
);
556 newdelta
.date
= olddate
;
558 newdelta
.date
= getcurdate(); /* use current date */
559 /* now check validity of date -- needed because of -d and -k */
561 cmpdate(newdelta
.date
,targetdelta
->date
) < 0) {
562 rcserror("Date %s precedes %s in revision %s.",
563 date2str(newdelta
.date
, newdatebuf
),
564 date2str(targetdelta
->date
, targetdatebuf
),
571 if (lockflag
&& addlock(&newdelta
, true) < 0) continue;
573 if (keepflag
&& *prevname
.string
)
574 if (addsymbol(newdelta
.num
, prevname
.string
, false) < 0)
576 if (!addsyms(newdelta
.num
))
581 puttree(Head
,frewrite
);
582 putdesc(false,textfile
);
584 changework
= Expand
< MIN_UNCHANGED_EXPAND
;
587 workdelta
= &newdelta
;
589 /* build rest of file */
591 diagnose("initial revision: %s\n", newdelta
.num
);
593 newdelta
.log
=getlogmsg();
594 putdftext(&newdelta
, workptr
, frewrite
, false);
595 RCSstat
.st_mode
= workstat
.st_mode
;
596 RCSstat
.st_nlink
= 0;
599 diffname
= maketemp(0);
600 newhead
= Head
== &newdelta
;
603 expname
= buildrevision(
604 gendeltas
, targetdelta
, (FILE*)0, false
608 strcmp(newdelta
.state
, targetdelta
->state
) == 0 &&
609 (changework
= rcsfcmp(
610 workptr
, &workstat
, expname
, targetdelta
613 diagnose("file is unchanged; reverting to previous revision %s\n",
616 if (removedlock
< lockflag
) {
617 diagnose("previous revision was not locked; ignoring -l option\n");
621 if (! (changedRCS
= lockflag
<removedlock
|| assoclst
))
622 workdelta
= targetdelta
;
625 * We have started to build the wrong new RCS file.
626 * Start over from the beginning.
628 long hwm
= ftell(frewrite
);
633 * Work around a common ftruncate() bug:
634 * NFS won't let you truncate a file that you
635 * currently lack permissions for, even if you
636 * had permissions when you opened it.
637 * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022
638 * says ftruncate might fail because it's not supported.
642 # define ftruncate(fd,length) (-1)
644 bad_truncate
= ftruncate(fileno(frewrite
), (off_t
)0);
650 if (!(workdelta
= genrevs(
651 targetdelta
->num
, (char*)0, (char*)0, (char*)0,
655 workdelta
->log
= targetdelta
->log
;
656 if (newdelta
.state
!= default_state
)
657 workdelta
->state
= newdelta
.state
;
658 if (lockthis
<removedlock
&& removelock(workdelta
)<0)
660 if (!addsyms(workdelta
->num
))
662 if (dorewrite(true, true) != 0)
664 fastcopy(finptr
, frewrite
);
666 while (ftell(frewrite
) < hwm
)
667 /* White out any earlier mistake with '\n's. */
668 /* This is unlikely. */
669 afputc('\n', frewrite
);
672 int wfd
= Ifileno(workptr
);
673 struct stat checkworkstat
;
674 char const *diffv
[6 + !!OPEN_O_BINARY
], **diffp
;
675 # if large_memory && !maps_memory
676 FILE *wfile
= workptr
->stream
;
679 # if !has_fflush_input && !(large_memory && maps_memory)
683 diagnose("new revision: %s; previous revision: %s\n",
684 newdelta
.num
, targetdelta
->num
686 newdelta
.log
= getlogmsg();
689 # if has_fflush_input
690 if (fflush(workptr
) != 0)
696 (wfile_off
= ftell(wfile
)) == -1
697 || fseek(wfile
, 0L, SEEK_SET
) != 0
698 # if has_fflush_input
699 || fflush(wfile
) != 0
705 # if !has_fflush_input && !(large_memory && maps_memory)
706 wfd_off
= lseek(wfd
, (off_t
)0, SEEK_CUR
);
709 && lseek(wfd
, (off_t
)0, SEEK_SET
) != 0))
714 *++diffp
= DIFFFLAGS
;
716 if (Expand
== BINARY_EXPAND
)
717 *++diffp
= "--binary";
719 *++diffp
= newhead
? "-" : expname
;
720 *++diffp
= newhead
? expname
: "-";
722 switch (runv(wfd
, diffname
, diffv
)) {
723 case DIFF_FAILURE
: case DIFF_SUCCESS
: break;
724 default: rcsfaterror("diff failed");
726 # if !has_fflush_input && !(large_memory && maps_memory)
727 if (lseek(wfd
, wfd_off
, SEEK_CUR
) == -1)
730 # if large_memory && !maps_memory
731 if (fseek(wfile
, wfile_off
, SEEK_SET
) != 0)
736 putdftext(&newdelta
, workptr
, frewrite
, false);
737 if (!putdtext(targetdelta
,diffname
,frewrite
,true)) continue;
739 if (!putdtext(&newdelta
,diffname
,frewrite
,true)) continue;
742 * Check whether the working file changed during checkin,
743 * to avoid producing an inconsistent RCS file.
746 fstat(wfd
, &checkworkstat
) != 0
747 || workstat
.st_mtime
!= checkworkstat
.st_mtime
748 || workstat
.st_size
!= checkworkstat
.st_size
750 workerror("file changed during checkin");
758 /* Deduce time_t of new revision if it is needed later. */
760 if (mtimeflag
| Ttimeflag
)
761 wtime
= date2time(workdelta
->date
);
763 if (donerewrite(changedRCS
,
764 !Ttimeflag
? (time_t)-1
765 : finptr
&& wtime
< RCSstat
.st_mtime
? RCSstat
.st_mtime
770 if (!keepworkingfile
) {
772 r
= un_link(workname
); /* Get rid of old file */
774 newworkmode
= WORKMODE(RCSstat
.st_mode
,
775 ! (Expand
==VAL_EXPAND
|| lockthis
< StrictLocks
)
777 mtime
= mtimeflag
? wtime
: (time_t)-1;
779 /* Expand if it might change or if we can't fix mode, time. */
780 if (changework
|| (r
=fixwork(newworkmode
,mtime
)) != 0) {
782 /* Expand keywords in file. */
783 locker_expansion
= lockthis
;
786 assoclst
? assoclst
->ssymbol
787 : keepflag
&& *prevname
.string
? prevname
.string
792 workptr
, workdelta
, &newworkname
, dolog
799 * No expansion occurred; try to reuse working file
800 * unless we already tried and failed.
803 if ((r
=fixwork(newworkmode
,mtime
)) == 0)
810 r
= chnamemod(&exfile
, newworkname
,
811 workname
, 1, newworkmode
, mtime
813 keepdirtemp(newworkname
);
827 exitmain(exitstatus
);
828 } /* end of main (ci) */
833 if (nerror
) exitstatus
= EXIT_FAILURE
;
843 # define exiterr ciExit
854 /*****************************************************************/
855 /* the rest are auxiliary routines */
860 /* Function: Appends a delta to the delta tree, whose number is
861 * given by newdelnum. Updates Head, newdelnum, newdelnumlength,
862 * and the links in newdelta.
863 * Return -1 on error, 1 if a lock is removed, 0 otherwise.
869 int newdnumlength
; /* actual length of new rev. num. */
871 newdnumlength
= countnumflds(newdelnum
.string
);
874 /* this covers non-existing RCS file and a file initialized with rcs -i */
875 if (newdnumlength
==0 && Dbranch
) {
876 bufscpy(&newdelnum
, Dbranch
);
877 newdnumlength
= countnumflds(Dbranch
);
879 if (newdnumlength
==0) bufscpy(&newdelnum
, "1.1");
880 else if (newdnumlength
==1) bufscat(&newdelnum
, ".1");
881 else if (newdnumlength
>2) {
882 rcserror("Branch point doesn't exist for revision %s.",
886 } /* newdnumlength == 2 is OK; */
891 if (newdnumlength
==0) {
892 /* derive new revision number from locks */
893 switch (findlock(true, &targetdelta
)) {
896 /* found two or more old locks */
900 /* found an old lock */
901 /* check whether locked revision exists */
902 if (!genrevs(targetdelta
->num
,(char*)0,(char*)0,(char*)0,&gendeltas
))
904 if (targetdelta
==Head
) {
908 } else if (!targetdelta
->next
&& countnumflds(targetdelta
->num
)>2) {
909 /* new tip revision on side branch */
910 targetdelta
->next
= &newdelta
;
913 /* middle revision; start a new branch */
914 bufscpy(&newdelnum
, "");
915 return addbranch(targetdelta
, &newdelnum
, 1);
917 incnum(targetdelta
->num
, &newdelnum
);
918 return 1; /* successful use of existing lock */
921 /* no existing lock; try Dbranch */
922 /* update newdelnum */
923 if (StrictLocks
|| !myself(RCSstat
.st_uid
)) {
924 rcserror("no lock set by %s", getcaller());
928 bufscpy(&newdelnum
, Dbranch
);
930 incnum(Head
->num
, &newdelnum
);
932 newdnumlength
= countnumflds(newdelnum
.string
);
933 /* now fall into next statement */
936 if (newdnumlength
<=2) {
937 /* add new head per given number */
938 if(newdnumlength
==1) {
939 /* make a two-field number out of it*/
940 if (cmpnumfld(newdelnum
.string
,Head
->num
,1)==0)
941 incnum(Head
->num
, &newdelnum
);
943 bufscat(&newdelnum
, ".1");
945 if (cmpnum(newdelnum
.string
,Head
->num
) <= 0) {
946 rcserror("revision %s too low; must be higher than %s",
947 newdelnum
.string
, Head
->num
952 if (0 <= (removedlock
= removelock(Head
))) {
953 if (!genrevs(Head
->num
,(char*)0,(char*)0,(char*)0,&gendeltas
))
955 newdelta
.next
= Head
;
960 /* put new revision on side branch */
961 /*first, get branch point */
962 tp
= newdelnum
.string
;
963 for (i
= newdnumlength
- ((newdnumlength
&1) ^ 1); --i
; )
966 *--tp
= 0; /* Kill final dot to get old delta temporarily. */
967 if (!(targetdelta
=genrevs(newdelnum
.string
,(char*)0,(char*)0,(char*)0,&gendeltas
)))
969 if (cmpnum(targetdelta
->num
, newdelnum
.string
) != 0) {
970 rcserror("can't find branch point %s", newdelnum
.string
);
973 *tp
= '.'; /* Restore final dot. */
974 return addbranch(targetdelta
, &newdelnum
, 0);
981 addbranch(branchpoint
, num
, removedlock
)
982 struct hshentry
*branchpoint
;
985 /* adds a new branch and branch delta at branchpoint.
986 * If num is the null string, appends the new branch, incrementing
987 * the highest branch number (initially 1), and setting the level number to 1.
988 * the new delta and branchhead are in globals newdelta and newbranch, resp.
989 * the new number is placed into num.
990 * Return -1 on error, 1 if a lock is removed, 0 otherwise.
991 * If REMOVEDLOCK is 1, a lock was already removed.
994 struct branchhead
*bhead
, **btrail
;
995 struct buf branchnum
;
997 int field
, numlength
;
998 static struct branchhead newbranch
; /* new branch to be inserted */
1000 numlength
= countnumflds(num
->string
);
1002 if (!branchpoint
->branches
) {
1003 /* start first branch */
1004 branchpoint
->branches
= &newbranch
;
1006 bufscpy(num
, branchpoint
->num
);
1007 bufscat(num
, ".1.1");
1008 } else if (numlength
&1)
1010 newbranch
.nextbranch
= 0;
1012 } else if (numlength
==0) {
1013 /* append new branch to the end */
1014 bhead
=branchpoint
->branches
;
1015 while (bhead
->nextbranch
) bhead
=bhead
->nextbranch
;
1016 bhead
->nextbranch
= &newbranch
;
1017 bufautobegin(&branchnum
);
1018 getbranchno(bhead
->hsh
->num
, &branchnum
);
1019 incnum(branchnum
.string
, num
);
1020 bufautoend(&branchnum
);
1022 newbranch
.nextbranch
= 0;
1024 /* place the branch properly */
1025 field
= numlength
- ((numlength
&1) ^ 1);
1026 /* field of branch number */
1027 btrail
= &branchpoint
->branches
;
1028 while (0 < (result
=cmpnumfld(num
->string
,(*btrail
)->hsh
->num
,field
))) {
1029 btrail
= &(*btrail
)->nextbranch
;
1036 /* insert/append new branchhead */
1037 newbranch
.nextbranch
= *btrail
;
1038 *btrail
= &newbranch
;
1039 if (numlength
&1) bufscat(num
, ".1");
1041 /* branch exists; append to end */
1042 bufautobegin(&branchnum
);
1043 getbranchno(num
->string
, &branchnum
);
1044 targetdelta
= genrevs(
1045 branchnum
.string
, (char*)0, (char*)0, (char*)0,
1048 bufautoend(&branchnum
);
1051 if (cmpnum(num
->string
,targetdelta
->num
) <= 0) {
1052 rcserror("revision %s too low; must be higher than %s",
1053 num
->string
, targetdelta
->num
1058 && 0 <= (removedlock
= removelock(targetdelta
))
1061 incnum(targetdelta
->num
,num
);
1062 targetdelta
->next
= &newdelta
;
1066 /* Don't do anything to newbranch. */
1069 newbranch
.hsh
= &newdelta
;
1071 if (branchpoint
->lockedby
)
1072 if (strcmp(branchpoint
->lockedby
, getcaller()) == 0)
1073 return removelock(branchpoint
); /* This returns 1. */
1081 register struct Symrev
*p
;
1083 for (p
= assoclst
; p
; p
= p
->nextsym
)
1084 if (addsymbol(num
, p
->ssymbol
, p
->override
) < 0)
1094 /* Increment the last field of revision number onum by one and
1095 * place the result into nnum.
1098 register char *tp
, *np
;
1102 bufalloc(nnum
, l
+2);
1103 np
= tp
= nnum
->string
;
1104 VOID
strcpy(np
, onum
);
1105 for (tp
= np
+ l
; np
!= tp
; )
1106 if (isdigit(*--tp
)) {
1116 /* We changed 999 to 000; now change it to 1000. */
1127 struct hshentry
* delta
;
1128 /* function: Finds the lock held by caller on delta,
1129 * removes it, and returns nonzero if successful.
1130 * Print an error message and return -1 if there is no such lock.
1131 * An exception is if !StrictLocks, and caller is the owner of
1132 * the RCS file. If caller does not have a lock in this case,
1133 * return 0; return 1 if a lock is actually removed.
1136 register struct rcslock
*next
, **trail
;
1140 for (trail
= &Locks
; (next
= *trail
); trail
= &next
->nextlock
)
1141 if (next
->delta
== delta
)
1142 if (strcmp(getcaller(), next
->login
) == 0) {
1143 /* We found a lock on delta by caller; delete it. */
1144 *trail
= next
->nextlock
;
1145 delta
->lockedby
= 0;
1148 rcserror("revision %s locked by %s", num
, next
->login
);
1151 if (!StrictLocks
&& myself(RCSstat
.st_uid
))
1153 rcserror("no lock set by %s for revision %s", getcaller(), num
);
1161 /* Return a pointer to the current date. */
1163 static char buffer
[datesize
]; /* date buffer */
1166 time2date(now(), buffer
);
1172 fixwork(mode_t newworkmode
, time_t mtime
)
1173 /* The `#if has_prototypes' is needed because mode_t might promote to int. */
1175 fixwork(newworkmode
, mtime
)
1181 1 < workstat
.st_nlink
1182 || (newworkmode
&S_IWUSR
&& !myself(workstat
.st_uid
))
1183 || setmtime(workname
, mtime
) != 0
1185 : workstat
.st_mode
== newworkmode
? 0
1187 : fchmod(Ifileno(workptr
), newworkmode
) == 0 ? 0
1192 : chmod(workname
, newworkmode
)
1198 xpandfile(unexfile
, delta
, exname
, dolog
)
1200 struct hshentry
const *delta
;
1201 char const **exname
;
1204 * Read unexfile and copy it to a
1205 * file, performing keyword substitution with data from delta.
1206 * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
1207 * If successful, stores the stream descriptor into *EXFILEP
1208 * and its name into *EXNAME.
1211 char const *targetname
;
1214 targetname
= makedirtemp(1);
1215 if (!(exfile
= fopenSafer(targetname
, FOPEN_W_WORK
))) {
1217 workerror("can't build working file");
1221 if (MIN_UNEXPAND
<= Expand
)
1222 fastcopy(unexfile
,exfile
);
1226 unexfile
, exfile
, delta
, false, (FILE*)0, dolog
1235 *exname
= targetname
;
1242 /* --------------------- G E T L O G M S G --------------------------------*/
1247 /* Obtain and yield a log message.
1248 * If a log message is given with -m, yield that message.
1249 * If this is the initial revision, yield a standard log message.
1250 * Otherwise, reads a character string from the terminal.
1251 * Stops after reading EOF or a single '.' on a
1252 * line. getlogmsg prompts the first time it is called for the
1253 * log message; during all later calls it asks whether the previous
1254 * log message can be reused.
1258 emptych
[] = EMPTYLOG
,
1259 initialch
[] = "Initial revision";
1260 static struct cbuf
const
1261 emptylog
= { emptych
, sizeof(emptych
)-sizeof(char) },
1262 initiallog
= { initialch
, sizeof(initialch
)-sizeof(char) };
1263 static struct buf logbuf
;
1264 static struct cbuf logmsg
;
1270 if (msg
.size
) return msg
;
1273 /* generate std. log message */
1274 caller
= getcaller();
1275 i
= sizeof(ciklog
)+strlen(caller
)+3;
1276 bufalloc(&logbuf
, i
+ datesize
+ zonelenmax
);
1278 VOID
sprintf(tp
, "%s%s at ", ciklog
, caller
);
1279 VOID
date2str(getcurdate(), tp
+i
);
1281 logmsg
.size
= strlen(tp
);
1285 if (!targetdelta
&& (
1286 cmpnum(newdelnum
.string
,"1.1")==0 ||
1287 cmpnum(newdelnum
.string
,"1.0")==0
1292 /*previous log available*/
1293 if (yesorno(true, "reuse log message of previous file? [yn](y): "))
1297 /* now read string from stdin */
1298 logmsg
= getsstdin("m", "log message", "", &logbuf
);
1300 /* now check whether the log message is not empty */
1306 /* Make a linked list of Symbolic names */
1309 addassoclst(flag
, sp
)
1315 pt
= talloc(struct Symrev
);
1317 pt
->override
= flag
;
1320 nextassoc
= &pt
->nextsym
;