1 /* Copyright (C) 1982, 1988, 1989 Walter Tichy
4 * Redistribution and use in source and binary forms are permitted
5 * provided that the above copyright notice and this paragraph are
6 * duplicated in all such forms and that any documentation,
7 * advertising materials, and other materials related to such
8 * distribution and use acknowledge that the software was developed
10 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
11 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
12 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
14 * Report all problems and direct all questions to:
15 * rcs-bugs@cs.purdue.edu
27 * RCS checkin operation
31 "$Header: /pub/NetBSD/misc/repositories/cvsroot/src/usr.bin/rcs/src/Attic/ci.c,v 1.1 1993/03/21 09:58:05 cgd Exp $ Purdue CS";
33 /*******************************************************************
34 * check revisions into RCS files
35 *******************************************************************
41 * Revision 4.9 89/05/01 15:10:54 narten
42 * changed copyright header to reflect current distribution rules
44 * Revision 4.8 88/11/08 13:38:23 narten
45 * changes from root@seismo.CSS.GOV (Super User)
46 * -d with no arguments uses the mod time of the file it is checking in
48 * Revision 4.7 88/11/08 10:59:04 narten
51 * Revision 4.7 88/08/09 19:12:07 eggert
52 * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
53 * Use execv(), not system(); allow cc -R; remove lint.
54 * isatty(fileno(stdin)) -> ttystdin()
56 * Revision 4.6 87/12/18 11:34:41 narten
57 * lint cleanups (from Guy Harris)
59 * Revision 4.5 87/10/18 10:18:48 narten
60 * Updating version numbers. Changes relative to revision 1.1 are actually
63 * Revision 1.3 87/09/24 13:57:19 narten
64 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
67 * Revision 1.2 87/03/27 14:21:33 jenkins
70 * Revision 1.1 84/01/23 14:49:54 kcs
73 * Revision 4.3 83/12/15 12:28:54 wft
74 * ci -u and ci -l now set mode of working file properly.
76 * Revision 4.2 83/12/05 13:40:54 wft
77 * Merged with 3.9.1.1: added calls to clearerr(stdin).
78 * made rewriteflag external.
80 * Revision 4.1 83/05/10 17:03:06 wft
81 * Added option -d and -w, and updated assingment of date, etc. to new delta.
82 * Added handling of default branches.
83 * Option -k generates std. log message; fixed undef. pointer in reading of log.
84 * Replaced getlock() with findlock(), link--unlink with rename(),
85 * getpwuid() with getcaller().
86 * Moved all revision number generation to new routine addelta().
87 * Removed calls to stat(); now done by pairfilenames().
88 * Changed most calls to catchints() with restoreints().
89 * Directed all interactive messages to stderr.
91 * Revision 3.9.1.1 83/10/19 04:21:03 lepreau
92 * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
94 * Revision 3.9 83/02/15 15:25:44 wft
97 * Revision 3.9 83/02/15 15:25:44 wft
98 * Added call to fastcopy() to copy remainder of RCS file.
100 * Revision 3.8 83/01/14 15:34:05 wft
101 * Added ignoring of interrupts while new RCS file is renamed;
102 * Avoids deletion of RCS files by interrupts.
104 * Revision 3.7 82/12/10 16:09:20 wft
105 * Corrected checking of return code from diff.
107 * Revision 3.6 82/12/08 21:34:49 wft
108 * Using DATEFORM to prepare date of checked-in revision;
109 * Fixed return from addbranch().
111 * Revision 3.5 82/12/04 18:32:42 wft
112 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
113 * field lockedby in removelock(), moved getlogmsg() before calling diff.
115 * Revision 3.4 82/12/02 13:27:13 wft
118 * Revision 3.3 82/11/28 20:53:31 wft
119 * Added mustcheckin() to check for redundant checkins.
120 * Added xpandfile() to do keyword expansion for -u and -l;
121 * -m appends linefeed to log message if necessary.
122 * getlogmsg() suppresses prompt if stdin is not a terminal.
123 * Replaced keeplock with lockflag, fclose() with ffclose(),
124 * %02d with %.2d, getlogin() with getpwuid().
126 * Revision 3.2 82/10/18 20:57:23 wft
127 * An RCS file inherits its mode during the first ci from the working file,
128 * otherwise it stays the same, except that write permission is removed.
129 * Fixed ci -l, added ci -u (both do an implicit co after the ci).
130 * Fixed call to getlogin(), added call to getfullRCSname(), added check
132 * Changed conflicting identifiers.
134 * Revision 3.1 82/10/13 16:04:59 wft
135 * fixed type of variables receiving from getc() (char -> int).
136 * added include file dbm.h for getting BYTESIZ. This is used
137 * to check the return code from diff portably.
142 static char rcsbaseid
[] = RCSBASE
;
144 #include <sys/types.h>
145 #include <sys/stat.h>
148 extern int rename(); /*rename files */
149 extern char * getcaller(); /*login of caller */
150 extern struct hshentry
* genrevs(); /*generate delta numbers */
151 extern quietflag
; /*suppresses diagnostics if true */
152 extern int nerror
; /*counter for errors */
153 extern char * buildrevision(); /*constructs desired revision */
154 extern char * checkid(); /*check identifiers */
155 extern int partime(); /*parse free-format date/time */
156 extern long maketime(); /*convert parsed time to unix time. */
157 extern long time(); /*get date and time */
158 extern struct tm
* localtime(); /*convert unixtime into tm-structure */
159 extern char * getdate(); /*formates current date (forward) */
160 extern char * mktempfile(); /*temporary file name generator */
161 extern struct lock
* addlock(); /*adds a new lock */
162 extern char * getlogmsg(); /*obtains log message; forward */
163 extern struct hshentry
* removelock(); /*finds a caller's lock (forward) */
164 extern struct hshentry
* findlock(); /*finds a lock */
165 extern char * xpandfile(); /*perform keyword expansion; forward */
167 extern char prevauthor
[];
168 extern char prevdate
[];
169 extern char prevrev
[];
170 extern char prevstate
[];
171 extern FILE * finptr
; /* RCS input file */
172 extern FILE * frewrite
; /* new RCS file */
173 extern int rewriteflag
; /* indicates whether input should be */
174 /* echoed to frewrite */
176 char * newRCSfilename
, * diffilename
;
177 char * RCSfilename
,*workfilename
,*expfilename
,*newworkfilename
;
178 extern struct stat RCSstat
, workstat
; /* file status of RCS and work file */
179 extern int haveRCSstat
, haveworkstat
;/* status indicators */
182 int copyflag
; /* indicates whether a string should be copied into memory*/
184 char * rev
, * state
, *msg
;
186 int initflag
, rcsinitflag
;
187 int lockflag
, keepworkingfile
,keepflag
;
188 int forceciflag
; /* forces check in */
189 int symrebindflag
; char * symbol
;
190 int textflag
; char * textfile
;
191 char * caller
; /* caller's login; */
192 char * author
; /* alternate author for -w option */
193 char altdate
[datelength
]; /* alternate date for -d */
194 int usestatdate
; /* use mod time of file for -d */
195 struct hshentry
* targetdelta
; /* old delta to be generated */
196 char * olddeltanum
; /* number of old delta */
197 struct hshentry
* gendeltas
[hshsize
]; /* stores deltas to be generated */
198 char newdelnum
[revlength
]; /* holds new revision number */
199 int newdnumlength
; /* actual length of new rev. num. */
200 char branchpointnum
[revlength
]; /* number of branchpoint */
201 struct hshentry newdelta
; /* new delta to be inserted */
202 struct branchhead newbranch
; /* new branch to be inserted */
203 char logmsg
[logsize
]; /* buffer for log message */
210 char * cmdusage
; /* holds command format */
211 struct stat filestatus
; /* used for getting the mode */
212 int msglen
; /* length of message given by -m */
213 int exit_stats
; /* return code for command invocations */
214 int newRCSmode
; /* mode for RCS file */
216 struct tm parseddate
, *ftm
;
220 cmdusage
= "command format:\nci -r[rev] -l[rev] -u[rev] -f[rev] -k[rev] -q[rev] -mmsg -nname -Nname -sstate -t[txtfile] file ...";
221 rev
= state
= msg
= symbol
= textfile
= nil
;
222 initflag
= rcsinitflag
= symrebindflag
= textflag
= quietflag
= false;
223 forceciflag
= lockflag
= keepworkingfile
= keepflag
= false;
224 caller
= getcaller(); author
= nil
; /* author may be reset by -w */
225 altdate
[0]= '\0'; /* empty alternate date for -d */
228 while (--argc
,++argv
, argc
>=1 && ((*argv
)[0] == '-')) {
229 switch ((*argv
)[1]) {
233 revno
: if ((*argv
)[2]!='\0') {
234 if (rev
!=nil
) warn("Redefinition of revision number");
240 keepworkingfile
=lockflag
=true;
244 keepworkingfile
=true; lockflag
=false;
260 if ((*argv
)[2]!='\0'){
261 if (msg
!=nil
)warn("Redefinition of -m option");
264 if (msglen
>= logsize
) {
265 warn("log message truncated to %d characters",
270 if (msg
[msglen
-1]!='\n') {
272 VOID
strcpy(logmsg
,msg
);msg
=logmsg
;
276 } else warn("Missing message for -m option");
281 if ((*argv
)[2]!='\0'){
282 if (symbol
!=nil
)warn("Redefinition of symbolic name");
284 if (!(nametest
=checkid(symbol
,' '))||*nametest
)
285 faterror("Name %s must be one word",symbol
);
286 } else warn("Missing name for -n option");
291 if ((*argv
)[2]!='\0'){
292 if (symbol
!=nil
)warn("Redefinition of symbolic name");
294 if (!(nametest
=checkid(symbol
,' '))||*nametest
)
295 faterror("Name %s must be one word",symbol
);
296 } else warn("Missing name for -N option");
300 if ((*argv
)[2]!='\0'){
301 if (state
!=nil
)warn("Redefinition of -s option");
303 VOID
checkid(state
,' ');
304 } else warn("Missing state for -s option");
309 if ((*argv
)[2]!='\0'){
310 if (textfile
!=nil
)warn("Redefinition of -t option");
311 textfile
= (*argv
)+2;
316 if ((*argv
)[2]!='\0'){
317 if (altdate
[0]!='\0' || usestatdate
==true)
318 warn("Redefinition of -d option");
319 /* process the date */
320 if ( partime((*argv
)+2, &parseddate
) == 0) {
321 faterror("Can't parse date/time: %s", (*argv
)+2);
324 if ( (unixtime
= maketime(&parseddate
)) == 0L) {
325 faterror("Inconsistent date/time: %s",(*argv
)+2);
328 ftm
= localtime(&unixtime
);
329 VOID
sprintf(altdate
,DATEFORM
,
330 ftm
->tm_year
,ftm
->tm_mon
+1,ftm
->tm_mday
,ftm
->tm_hour
,ftm
->tm_min
,ftm
->tm_sec
);
336 if ((*argv
)[2]!='\0'){
337 if (author
!=nil
)warn("Redefinition of -w option");
339 VOID
checkid(author
,' ');
340 } else warn("Missing author for -w option");
347 faterror("unknown option: %s\n%s", *argv
,cmdusage
);
349 } /* end processing of options */
351 if (argc
<1) faterror("No input file\n%s",cmdusage
);
353 if (!ttystdin() && msg
==nil
&& textflag
&& textfile
==nil
) {
354 /* would need both log message and descriptive text from a file */
355 faterror("Can't take both log and description from redirected stdin; use -ttextfile");
357 /* now handle all filenames */
360 copyflag
=rewriteflag
=false;
361 finptr
=frewrite
=NULL
;
365 switch (pairfilenames(argc
,argv
,false,false)) {
367 case -1: /* New RCS file */
368 initflag
=true; rcsinitflag
=false;
374 case 1: /* Normal checkin with prev . RCS file */
375 initflag
=false; rcsinitflag
=(Head
==nil
);
378 /* now RCSfilename contains the name of the RCS file, and
379 * workfilename contains the name of the working file.
380 * if !initflag, finptr contains the file descriptor for the
381 * RCS file. The admin node is initialized.
382 * workstat and RCSstat are set.
385 diagnose("%s <-- %s", RCSfilename
,workfilename
);
387 if (access(workfilename
,4)!=0) {
388 error("working file %s not readable or nonexistent",
394 * make sure workfile is a regular file.
396 VOID
stat(workfilename
, &filestatus
);
397 if ((filestatus
.st_mode
& S_IFMT
) != S_IFREG
) {
398 error("working file %s isn't a regular file", workfilename
);
403 * if RCSfile doesn't exist, use mode from workfile, otherwise
404 * keep the one from the RCSfile.
406 if (! (initflag
|| rcsinitflag
))
407 VOID
fstat(fileno(finptr
), &filestatus
);
409 if (!trydiraccess(RCSfilename
)) continue; /* give up */
410 if (!initflag
&& !checkaccesslist(caller
)) continue; /* give up */
411 if (!trysema(RCSfilename
,true)) continue; /* give up */
414 /* get keyword values from working file */
415 if (!getoldkeys(workfilename
)) continue;
416 if (rev
==nil
&& *(rev
=prevrev
)=='\0') {
417 error("Can't find a revision number in %s",workfilename
);
420 if (*prevdate
=='\0' && *altdate
=='\0' && usestatdate
==false)
421 warn("Can't find a date in %s",workfilename
);
422 if (*prevauthor
=='\0' && author
==nil
)
423 warn("Can't find an author in %s", workfilename
);
424 if (*prevstate
=='\0' && state
==nil
)
425 warn("Can't find a state in %s", workfilename
);
426 } /* end processing keepflag */
428 gettree(); /* reads in the delta tree.*/
430 /* expand symbolic revision number */
431 if (!expandsym(rev
,newdelnum
)) continue;
433 /* splice new delta into tree */
434 if (!addelta()) continue;
436 if (initflag
||rcsinitflag
) {
437 diagnose("initial revision: %s",newdelnum
);
438 } else diagnose("new revision: %s; previous revision: %s",
439 newdelnum
,olddeltanum
);
441 newdelta
.num
=newdelnum
;
442 newdelta
.branches
=nil
;
444 newdelta
.lockedby
=nil
; /*might be changed by addlock() */
447 newdelta
.author
=author
; /* set author given by -w */
448 elsif (keepflag
&& *prevauthor
!='\0')
449 newdelta
.author
=prevauthor
; /* preserve old author of possible*/
450 else newdelta
.author
=caller
; /* otherwise use caller's id */
452 newdelta
.state
=state
; /* set state given by -s */
453 elsif (keepflag
&& *prevstate
!='\0')
454 newdelta
.state
=prevstate
; /* preserve old state if possilbe */
455 else newdelta
.state
=DEFAULTSTATE
;/* otherwise use default state */
456 if (usestatdate
==true) {
458 error("can't stat %s",workfilename
);
461 ftm
= localtime(&workstat
.st_mtime
);
462 VOID
sprintf(altdate
,DATEFORM
,ftm
->tm_year
,ftm
->tm_mon
+1,
463 ftm
->tm_mday
,ftm
->tm_hour
,ftm
->tm_min
,ftm
->tm_sec
);
466 newdelta
.date
=altdate
; /* set date given by -d */
467 elsif (keepflag
&& *prevdate
!='\0') /* preserve old date if possible */
468 newdelta
.date
=prevdate
;
470 newdelta
.date
= getdate(); /* use current date */
471 /* now check validity of date -- needed because of -d and -k */
472 if (targetdelta
!=nil
&&
473 cmpnum(newdelta
.date
,targetdelta
->date
)<=0) {
474 error("Date %s is not later than %s in existing revision %s",
475 newdelta
.date
,targetdelta
->date
, targetdelta
->num
);
480 if (lockflag
&& !addlock(&newdelta
,caller
)) continue;
481 if (symbol
&& !addsymbol(&newdelta
,symbol
,symrebindflag
)) continue;
483 /* prepare for rewriting the RCS file */
484 newRCSfilename
=mktempfile(RCSfilename
,NEWRCSFILE
);
485 if ((frewrite
=fopen(newRCSfilename
, "w"))==NULL
) {
486 error("Can't open file %s",newRCSfilename
);
490 puttree(Head
,frewrite
);
491 putdesc(initflag
,textflag
,textfile
,quietflag
);
494 /* build rest of file */
495 if (initflag
||rcsinitflag
) {
497 newdelta
.log
=getlogmsg();
498 if(!putdtext(newdelnum
,newdelta
.log
,workfilename
,frewrite
)) continue;
499 ffclose(frewrite
); frewrite
=NULL
;
501 diffilename
=mktempfile("/tmp/",DIFFILE
);
502 if (&newdelta
==Head
) {
503 /* prepend new one */
506 buildrevision(gendeltas
,targetdelta
,"/tmp/",false))) continue;
507 if (!mustcheckin(expfilename
,targetdelta
)) continue;
508 /* don't check in files that aren't different, unless forced*/
509 newdelta
.log
=getlogmsg();
510 exit_stats
= run((char*)nil
, diffilename
,
511 DIFF
,"-n",workfilename
,expfilename
, (char*)nil
);
512 if (exit_stats
!= 0 && exit_stats
!= (1 << BYTESIZ
))
513 faterror ("diff failed");
514 /* diff returns 2 in the upper byte on failure */
515 if(!putdtext(newdelnum
,newdelta
.log
,workfilename
,frewrite
)) continue;
516 if(!putdtext(olddeltanum
,targetdelta
->log
,diffilename
,frewrite
)) continue;
518 /* insert new delta text */
521 buildrevision(gendeltas
,targetdelta
,"/tmp/",false))) continue;
522 if (!mustcheckin(expfilename
,targetdelta
)) continue;
523 /* don't check in files that aren't different, unless forced*/
524 newdelta
.log
=getlogmsg();
525 exit_stats
= run((char*)nil
, diffilename
,
526 DIFF
,"-n",expfilename
,workfilename
, (char*)nil
);
527 if (exit_stats
!= 0 && exit_stats
!= (1 << BYTESIZ
))
528 faterror ("diff failed");
529 if(!putdtext(newdelnum
,newdelta
.log
,diffilename
,frewrite
)) continue;
532 /* rewrite rest of RCS file */
533 fastcopy(finptr
,frewrite
);
534 ffclose(frewrite
); frewrite
=NULL
;
537 if (rename(newRCSfilename
,RCSfilename
)<0) {
538 error("Can't write new RCS file %s; saved in %s",
539 RCSfilename
,newRCSfilename
);
540 newRCSfilename
[0]='\0'; /* avoid deletion by cleanup*/
545 newRCSfilename
[0]='\0'; /* avoid re-unlinking by cleanup()*/
547 newRCSmode
= (initflag
|rcsinitflag
?workstat
.st_mode
:RCSstat
.st_mode
)& ~0222;
548 /* newRCSmode is also used to adjust mode of working file for -u and -l */
549 if (chmod(RCSfilename
,newRCSmode
)<0)
550 warn("Can't set mode of %s",RCSfilename
);
554 logcommand("ci",&newdelta
,gendeltas
,caller
);
557 if (!keepworkingfile
) {
558 VOID
unlink(workfilename
); /* get rid of old file */
560 /* expand keywords in file */
562 xpandfile(workfilename
,workfilename
/*for directory*/,&newdelta
);
563 if (!newworkfilename
) continue; /* expand failed */
565 if (rename(newworkfilename
,workfilename
) <0) {
566 error("Can't expand keywords in %s",workfilename
);
570 newworkfilename
[0]='\0'; /* avoid re-unlink by cleanup */
571 if (chmod(workfilename
, WORKMODE(newRCSmode
))<0)
572 warn("Can't adjust mode of %s",workfilename
);
582 } /* end of main (ci) */
583 /*****************************************************************/
584 /* the rest are auxiliary routines */
588 /* Function: Appends a delta to the delta tree, whose number is
589 * given by newdelnum[]. Updates Head, newdelnum, newdenumlength,
590 * olddeltanum and the links in newdelta.
591 * Retruns false on error, true on success.
594 register char * sp
, * tp
;
597 newdnumlength
=countnumflds(newdelnum
);
599 if (initflag
|| rcsinitflag
) {
600 /* this covers non-existing RCS file and a file initialized with rcs -i */
601 if ((newdnumlength
==0)&&(Dbranch
!=nil
)) {
602 VOID
strcpy(newdelnum
,Dbranch
->num
);
603 newdnumlength
=countnumflds(newdelnum
);
605 if (newdnumlength
==0) VOID
strcpy(newdelnum
,"1.1");
606 elsif (newdnumlength
==1) VOID
strcat(newdelnum
,".1");
607 elsif (newdnumlength
>2) {
608 error("Branch point does not exist for %s",newdelnum
);
610 } /* newdnumlength == 2 is OK; */
616 if (newdnumlength
==0) {
617 /* derive new revision number from locks */
618 targetdelta
=findlock(caller
,true); /*find and delete it*/
620 /* found an old lock */
621 olddeltanum
=targetdelta
->num
;
622 /* check whether locked revision exists */
623 if (!genrevs(olddeltanum
,(char *)nil
,(char *)nil
,(char *)nil
,gendeltas
)) return false;
624 if (targetdelta
==Head
) {
628 incnum(olddeltanum
, newdelnum
);
629 } elsif ((targetdelta
->next
==nil
)&&(countnumflds(olddeltanum
)>2)) {
630 /* new tip revision on side branch */
631 targetdelta
->next
= &newdelta
;
633 incnum(olddeltanum
, newdelnum
);
635 /* middle revision; start a new branch */
637 if (!addbranch(targetdelta
,newdelnum
)) return false;
639 return true; /* successfull use of existing lock */
641 /* no existing lock; try Dbranch */
642 /* update newdelnum */
643 if (!((StrictLocks
==false) && (getuid() == RCSstat
.st_uid
))) {
644 error("no lock set by %s",caller
);
648 VOID
strcpy(newdelnum
,Dbranch
->num
);
650 incnum(Head
->num
,newdelnum
);
652 newdnumlength
=countnumflds(newdelnum
);
653 /* now fall into next statement */
656 if (newdnumlength
<=2) {
657 /* add new head per given number */
658 olddeltanum
=Head
->num
;
659 if(newdnumlength
==1) {
660 /* make a two-field number out of it*/
661 if (cmpnumfld(newdelnum
,olddeltanum
,1)==0)
662 incnum(olddeltanum
,newdelnum
);
663 else VOID
strcat(newdelnum
, ".1");
665 if (cmpnum(newdelnum
,olddeltanum
) <= 0) {
666 error("deltanumber %s too low; must be higher than %s",
667 newdelnum
,Head
->num
);
670 if (!(targetdelta
=removelock(caller
,Head
))) return false;
671 if (!(genrevs(olddeltanum
,(char *)nil
,(char *)nil
,(char *)nil
,gendeltas
))) return false;
675 /* put new revision on side branch */
676 /*first, get branch point */
677 tp
=branchpointnum
; sp
=newdelnum
;
678 for(i
=newdnumlength
-(newdnumlength
%2==1?1:2);i
>0;i
--) {
679 while (*sp
!= '.') *tp
++ = *sp
++; /*copy field*/
680 *tp
++ = *sp
++; /*copy dot */
682 *(tp
-1) = '\0'; /* kill final dot */
683 olddeltanum
=branchpointnum
; /*temporary old delta*/
684 if (!(targetdelta
=genrevs(branchpointnum
,(char *)nil
,(char *)nil
,(char *)nil
,gendeltas
)))
686 if (cmpnum(targetdelta
->num
,branchpointnum
)!=0) {
687 error("Cannot find branchpoint %s",branchpointnum
);
690 if (!addbranch(targetdelta
,newdelnum
)) return false;
697 int addbranch(branchpoint
,num
)
698 struct hshentry
* branchpoint
;
700 /* adds a new branch and branch delta at branchpoint.
701 * If num is the null string, appends the new branch, incrementing
702 * the highest branch number (initially 1), and setting the level number to 1.
703 * the new delta and branchhead are in globals newdelta and newbranch, resp.
704 * the new number is placed into num.
705 * returns false on error.
708 struct branchhead
* bhead
, * btrail
;
709 char branchnum
[revlength
];
710 int numlength
, result
, field
;
712 numlength
= countnumflds(num
);
714 if (branchpoint
->branches
==nil
) {
715 /* start first branch */
716 branchpoint
->branches
= &newbranch
;
718 VOID
strcpy(num
, branchpoint
->num
);
719 VOID
strcat(num
,".1.1");
720 } elsif(countnumflds(num
)%2 == 1)
721 VOID
strcat(num
, ".1");
722 newbranch
.nextbranch
=nil
;
724 } elsif (numlength
==0) {
725 /* append new branch to the end */
726 bhead
=branchpoint
->branches
;
727 while (bhead
->nextbranch
) bhead
=bhead
->nextbranch
;
728 bhead
->nextbranch
= &newbranch
;
729 getbranchno(bhead
->hsh
->num
,branchnum
);
730 incnum(branchnum
,num
);
731 VOID
strcat(num
,".1");
732 newbranch
.nextbranch
=nil
;
734 /* place the branch properly */
735 field
= numlength
- (numlength
%2 ==1?0:1);
736 /* field of branch number */
737 bhead
=branchpoint
->branches
;
738 while ((bhead
!=nil
) &&
739 ((result
=cmpnumfld(num
,bhead
->hsh
->num
,field
))>0)) {
741 bhead
=bhead
->nextbranch
;
743 if (bhead
==nil
|| result
<0) {
744 /* insert/append new branchhead */
745 if (bhead
==branchpoint
->branches
)
746 branchpoint
->branches
= &newbranch
;
747 else btrail
->nextbranch
= &newbranch
;
748 newbranch
.nextbranch
=bhead
;
749 if (numlength
%2 ==1) VOID
strcat(num
,".1");
751 /* branch exists; append to end */
752 getbranchno(num
,branchnum
);
753 if (!(targetdelta
=genrevs(branchnum
,(char *)nil
,(char *)nil
,(char *)nil
,
754 gendeltas
))) return false;
755 olddeltanum
=targetdelta
->num
;
756 if (cmpnum(num
,olddeltanum
) <= 0) {
757 error("deltanumber %s too low; must be higher than %s",
761 if (!removelock(caller
,targetdelta
)) return false;
762 if (numlength
%2==1) incnum(olddeltanum
,num
);
763 targetdelta
->next
= &newdelta
;
765 return true; /* Don't do anything to newbranch */
768 newbranch
.hsh
= &newdelta
;
775 struct hshentry
* removelock(who
,delta
)
776 char * who
; struct hshentry
* delta
;
777 /* function: Finds the lock held by who on delta,
778 * removes it, and returns a pointer to the delta.
779 * Prints an error message and returns nil if there is no such lock.
780 * An exception is if StrictLocks==false, and who is the owner of
781 * the RCS file. If who does not have a lock in this case,
785 register struct lock
* next
, * trail
;
788 int whomatch
, nummatch
;
791 dummy
.nextlock
=next
=Locks
;
794 whomatch
=strcmp(who
,next
->login
);
795 nummatch
=strcmp(num
,next
->delta
->num
);
796 if ((whomatch
==0) && (nummatch
==0)) break;
797 /*found a lock on delta by who*/
798 if ((whomatch
!=0)&&(nummatch
==0)) {
799 error("revision %s locked by %s",num
,next
->login
);
806 /*found one; delete it */
807 trail
->nextlock
=next
->nextlock
;
808 Locks
=dummy
.nextlock
;
809 next
->delta
->lockedby
=nil
; /* reset locked-by */
812 if (!((StrictLocks
==false) && (getuid() == RCSstat
.st_uid
))) {
813 error("no lock set by %s for revision %s",who
,num
);
824 /* Function: returns a pointer to the current date in the form
825 * YY.MM.DD.hh.mm.ss\0
830 static char buffer
[datelength
]; /* date buffer */
831 clock
=time((long *)0);
832 tm
=localtime(&clock
);
833 VOID
sprintf(buffer
, DATEFORM
,
834 tm
->tm_year
, tm
->tm_mon
+1, tm
->tm_mday
,
835 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
840 char * xpandfile (unexfname
,dir
,delta
)
841 char * unexfname
, * dir
;
842 struct hshentry
* delta
;
843 /* Function: Reads file unexpfname and copies it to a
844 * file in dir, performing keyword substitution with data from delta.
845 * returns the name of the expanded file if successful, nil otherwise.
847 { char * targetfname
;
848 FILE * unexfile
, *exfile
;
850 targetfname
=mktempfile(dir
,TMPFILE3
);
851 if ((unexfile
=fopen(unexfname
, "r" ))==NULL
||
852 (exfile
=fopen(targetfname
,"w"))==NULL
) {
853 error("Can't expand file %s",unexfname
);
856 while (expandline(unexfile
,exfile
,delta
,false,false)); /*expand*/
857 ffclose(unexfile
);ffclose(exfile
);
862 mustcheckin (unexfname
,delta
)
863 char * unexfname
; struct hshentry
* delta
;
864 /* Function: determines whether checkin should proceed.
865 * Compares the wrkfilename with unexfname, disregarding keywords.
866 * If the 2 files differ, returns true. If they do not differ, asks the user
867 * whether to return true or false (i.e., whether to checkin the file anyway.
868 * If the files do not differ, and quietflag==true, returns false.
869 * Shortcut: If forceciflag==true, mustcheckin() always returns true.
872 int response
, result
;
874 if (forceciflag
) return true;
876 if (!rcsfcmp(workfilename
,unexfname
,delta
)) return true;
877 /* If files are different, must check them in. */
879 /* files are the same */
880 diagnose("File %s is unchanged with respect to revision %s",
881 workfilename
,delta
->num
);
882 if (quietflag
|| !ttystdin()) {
883 /* Files are the same, but can't ask, so don't checkin*/
886 /* ask user whether to check in */
887 VOID
fputs("checkin anyway? [ny](n): ",stderr
);
888 response
=c
=getchar();
889 while (!(c
==EOF
|| c
=='\n')) c
=getchar();/*skip to end of line*/
890 result
=(response
=='y'||response
=='Y');
894 warn("checkin aborted since %s was not changed; %s %sdeleted.",
895 workfilename
,workfilename
,keepworkingfile
?"not ":"");
897 diagnose("checkin aborted; %s %sdeleted.",
898 workfilename
,keepworkingfile
?"not ":"");
900 if (!keepworkingfile
) VOID
unlink(workfilename
);
908 /* --------------------- G E T L O G M S G --------------------------------*/
909 extern int stdinread
; /* is >0 if redirected stdin has been read once. */
913 /* Function: obtains a log message and returns a pointer to it.
914 * If a log message is given via the -m option, a pointer to that
915 * string is returned.
916 * If this is the initial revision, a standard log message is returned.
917 * Otherwise, reads a character string from the terminal.
918 * The string must be terminated with a control-d or a single '.' on a
919 * line. getlogmsg prompts the first time it is called for the
920 * log message; during all later calls it asks whether the previous
921 * log message can be reused.
922 * returns a pointer to the character string; the pointer is always non-nil.
925 static logyet
; /*indicates whether previous log present*/
926 static char emptylog
[] = "*** empty log message ***\n";
927 static char initiallog
[]= "Initial revision\n";
930 register char c
, old1
, old2
, * tp
;
934 if ((olddeltanum
==nil
)&&
935 ((cmpnum(newdelnum
,"1.1")==0)||(cmpnum(newdelnum
,"1.0")==0))) {
939 /* generate std. log message */
940 VOID
sprintf(logmsg
, "checked in with -k by %s at %s.\n",caller
,getdate());
944 /*previous log available*/
945 if (!ttystdin()) return logmsg
; /* reuse if stdin is not a terminal*/
947 clearerr(stdin
); /* reset EOF ptr */
948 VOID
fputs("reuse log message of previous file? [yn](y): ",stderr
);
951 while (!(cin
==EOF
|| cin
=='\n')) cin
=getchar();/*skip to end of line*/
952 if (response
=='\n'||response
=='y'||response
=='Y')
955 logmsg
[0]='\0'; /*kill existing log message */
958 /* now read string from stdin */
960 VOID
fputs("enter log message:\n(terminate with ^D or single '.')\n>> ",stderr
);
961 } else { /* redirected stdin */
963 faterror("Can't reread redirected stdin for log message; use -m");
967 tp
=logmsg
; old1
='\n'; old2
=' ';
977 if ((tp
==logmsg
)||(*(tp
-1)!='\n')) *tp
++ = '\n'; /* append newline */
978 *tp
= '\0'; /*terminate*/
981 if (cin
=='\n' && old1
=='.' && old2
=='\n') {
982 *(tp
-1) = '\0'; /*kill last period */
985 if (tp
>=logmsg
+logsize
-2) { /* overflow */
987 warn("log message truncated to %d characters",logsize
);
988 logmsg
[logsize
-2]='\n';logmsg
[logsize
-1]='\0';
991 VOID
fprintf(stderr
,"log message too long. Maximum: %d\n",logsize
);
992 VOID
fputs("reenter log message:\n>> ",stderr
);
993 tp
=logmsg
; old1
='\n'; old2
=' ';
994 while (cin
!='\n') cin
=getchar(); /*skip line */
997 if (cin
=='\n' && ttystdin()) VOID
fputs(">> ",stderr
);
998 *tp
++ = cin
; old2
=old1
; old1
=cin
; /* this is the actual work!*/
999 /*SDELIM will be changed to double SDELIM by putdtext*/
1002 /* now check whether the log message is not empty */
1004 while ((c
= *tp
++)==' '||c
=='\t'||c
=='\n'||c
=='\f');