No empty .Rs/.Re
[netbsd-mini2440.git] / usr.bin / rcs / src / ci.c
blob74ee0f9a060613c62f411a80c1029e9c93409905
1 /* Copyright (C) 1982, 1988, 1989 Walter Tichy
2 * All rights reserved.
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
9 * by Walter Tichy.
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
29 #ifndef lint
30 static char rcsid[]=
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";
32 #endif
33 /*******************************************************************
34 * check revisions into RCS files
35 *******************************************************************
40 /* $Log: ci.c,v $
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
49 * changes from eggert
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
61 * relative to 4.3
63 * Revision 1.3 87/09/24 13:57:19 narten
64 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
65 * warnings)
67 * Revision 1.2 87/03/27 14:21:33 jenkins
68 * Port to suns
70 * Revision 1.1 84/01/23 14:49:54 kcs
71 * Initial revision
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
95 * 4.2 prerelease
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
116 * added option -k.
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
131 * for write error.
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.
140 #include "rcsbase.h"
141 #ifndef lint
142 static char rcsbaseid[] = RCSBASE;
143 #endif
144 #include <sys/types.h>
145 #include <sys/stat.h>
146 #include "time.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 */
205 main (argc, argv)
206 int argc;
207 char * argv[];
209 char * nametest;
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 */
215 long unixtime;
216 struct tm parseddate, *ftm;
218 catchints();
219 cmdid = "ci";
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 */
226 usestatdate=false;
228 while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
229 switch ((*argv)[1]) {
231 case 'r':
232 lockflag=false;
233 revno: if ((*argv)[2]!='\0') {
234 if (rev!=nil) warn("Redefinition of revision number");
235 rev = (*argv)+2;
237 break;
239 case 'l':
240 keepworkingfile=lockflag=true;
241 goto revno;
243 case 'u':
244 keepworkingfile=true; lockflag=false;
245 goto revno;
247 case 'q':
248 quietflag=true;
249 goto revno;
251 case 'f':
252 forceciflag=true;
253 goto revno;
255 case 'k':
256 keepflag=true;
257 goto revno;
259 case 'm':
260 if ((*argv)[2]!='\0'){
261 if (msg!=nil)warn("Redefinition of -m option");
262 msg = (*argv)+2;
263 msglen=strlen(msg);
264 if (msglen >= logsize) {
265 warn("log message truncated to %d characters",
266 logsize);
267 msg[logsize-2]='\n';
268 msg[logsize-1]='\0';
270 if (msg[msglen-1]!='\n') {
271 /*append linefeed*/
272 VOID strcpy(logmsg,msg);msg=logmsg;
273 msg[msglen] = '\n';
274 msg[++msglen]= '\0';
276 } else warn("Missing message for -m option");
277 break;
279 case 'n':
280 symrebindflag=false;
281 if ((*argv)[2]!='\0'){
282 if (symbol!=nil)warn("Redefinition of symbolic name");
283 symbol = (*argv)+2;
284 if (!(nametest=checkid(symbol,' '))||*nametest)
285 faterror("Name %s must be one word",symbol);
286 } else warn("Missing name for -n option");
287 break;
289 case 'N':
290 symrebindflag=true;
291 if ((*argv)[2]!='\0'){
292 if (symbol!=nil)warn("Redefinition of symbolic name");
293 symbol = (*argv)+2;
294 if (!(nametest=checkid(symbol,' '))||*nametest)
295 faterror("Name %s must be one word",symbol);
296 } else warn("Missing name for -N option");
297 break;
299 case 's':
300 if ((*argv)[2]!='\0'){
301 if (state!=nil)warn("Redefinition of -s option");
302 state = (*argv)+2;
303 VOID checkid(state,' ');
304 } else warn("Missing state for -s option");
305 break;
307 case 't':
308 textflag=true;
309 if ((*argv)[2]!='\0'){
310 if (textfile!=nil)warn("Redefinition of -t option");
311 textfile = (*argv)+2;
313 break;
315 case 'd':
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);
322 break;
324 if ( (unixtime = maketime(&parseddate)) == 0L) {
325 faterror("Inconsistent date/time: %s",(*argv)+2);
326 break;
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);
331 } else
332 usestatdate++;
333 break;
335 case 'w':
336 if ((*argv)[2]!='\0'){
337 if (author!=nil)warn("Redefinition of -w option");
338 author = (*argv)+2;
339 VOID checkid(author,' ');
340 } else warn("Missing author for -w option");
341 break;
346 default:
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 */
358 do {
359 gendeltas[0] = nil;
360 copyflag=rewriteflag=false;
361 finptr=frewrite=NULL;
362 targetdelta=nil;
363 olddeltanum=nil;
365 switch (pairfilenames(argc,argv,false,false)) {
367 case -1: /* New RCS file */
368 initflag=true; rcsinitflag=false;
369 break;
371 case 0: /* Error */
372 continue;
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",
389 workfilename);
390 continue;
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);
399 continue;
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 */
413 if (keepflag) {
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);
418 continue;
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;
443 newdelta.log=nil;
444 newdelta.lockedby=nil; /*might be changed by addlock() */
445 /* set author */
446 if (author!=nil)
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 */
451 if (state!=nil)
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) {
457 if(haveworkstat<0) {
458 error("can't stat %s",workfilename);
459 continue;
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);
465 if (*altdate!='\0')
466 newdelta.date=altdate; /* set date given by -d */
467 elsif (keepflag && *prevdate!='\0') /* preserve old date if possible */
468 newdelta.date =prevdate;
469 else
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);
476 continue;
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);
487 continue;
489 putadmin(frewrite);
490 puttree(Head,frewrite);
491 putdesc(initflag,textflag,textfile,quietflag);
494 /* build rest of file */
495 if (initflag||rcsinitflag) {
496 /* get logmessage */
497 newdelta.log=getlogmsg();
498 if(!putdtext(newdelnum,newdelta.log,workfilename,frewrite)) continue;
499 ffclose(frewrite); frewrite=NULL;
500 } else {
501 diffilename=mktempfile("/tmp/",DIFFILE);
502 if (&newdelta==Head) {
503 /* prepend new one */
504 rewriteflag=false;
505 if (!(expfilename=
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;
517 } else {
518 /* insert new delta text */
519 rewriteflag=true;
520 if (!(expfilename=
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;
536 ignoreints();
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*/
541 restoreints();
542 VOID cleanup();
543 break;
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);
552 restoreints();
553 # ifdef SNOOPFILE
554 logcommand("ci",&newdelta,gendeltas,caller);
555 # endif
557 if (!keepworkingfile) {
558 VOID unlink(workfilename); /* get rid of old file */
559 } else {
560 /* expand keywords in file */
561 newworkfilename=
562 xpandfile(workfilename,workfilename /*for directory*/,&newdelta);
563 if (!newworkfilename) continue; /* expand failed */
564 ignoreints();
565 if (rename(newworkfilename,workfilename) <0) {
566 error("Can't expand keywords in %s",workfilename);
567 restoreints();
568 continue;
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);
573 restoreints();
575 diagnose("done");
577 } while (cleanup(),
578 ++argv, --argc >=1);
580 exit(nerror!=0);
581 /*NOTREACHED*/
582 } /* end of main (ci) */
583 /*****************************************************************/
584 /* the rest are auxiliary routines */
587 int addelta()
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;
595 register int i;
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);
609 return false;
610 } /* newdnumlength == 2 is OK; */
611 olddeltanum=nil;
612 Head = &newdelta;
613 newdelta.next=nil;
614 return true;
616 if (newdnumlength==0) {
617 /* derive new revision number from locks */
618 targetdelta=findlock(caller,true); /*find and delete it*/
619 if (targetdelta) {
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) {
625 /* make new head */
626 newdelta.next=Head;
627 Head= &newdelta;
628 incnum(olddeltanum, newdelnum);
629 } elsif ((targetdelta->next==nil)&&(countnumflds(olddeltanum)>2)) {
630 /* new tip revision on side branch */
631 targetdelta->next= &newdelta;
632 newdelta.next = nil;
633 incnum(olddeltanum, newdelnum);
634 } else {
635 /* middle revision; start a new branch */
636 newdelnum[0]='\0';
637 if (!addbranch(targetdelta,newdelnum)) return false;
639 return true; /* successfull use of existing lock */
640 } else {
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);
645 return false;
647 if (Dbranch) {
648 VOID strcpy(newdelnum,Dbranch->num);
649 } else {
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);
668 return false;
670 if (!(targetdelta=removelock(caller,Head))) return false;
671 if (!(genrevs(olddeltanum,(char *)nil,(char *)nil,(char *)nil,gendeltas))) return false;
672 newdelta.next=Head;
673 Head= &newdelta;
674 } else {
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)))
685 return false;
686 if (cmpnum(targetdelta->num,branchpointnum)!=0) {
687 error("Cannot find branchpoint %s",branchpointnum);
688 return false;
690 if (!addbranch(targetdelta,newdelnum)) return false;
692 return true;
697 int addbranch(branchpoint,num)
698 struct hshentry * branchpoint;
699 char * num;
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;
717 if (numlength==0) {
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;
733 } else {
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)) {
740 btrail=bhead;
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");
750 } else {
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",
758 num,olddeltanum);
759 return false;
761 if (!removelock(caller,targetdelta)) return false;
762 if (numlength%2==1) incnum(olddeltanum,num);
763 targetdelta->next= &newdelta;
764 newdelta.next=nil;
765 return true; /* Don't do anything to newbranch */
768 newbranch.hsh = &newdelta;
769 newdelta.next=nil;
770 return true;
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,
782 * delta is returned.
785 register struct lock * next, * trail;
786 char * num;
787 struct lock dummy;
788 int whomatch, nummatch;
790 num=delta->num;
791 dummy.nextlock=next=Locks;
792 trail = &dummy;
793 while (next!=nil) {
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);
800 return nil;
802 trail=next;
803 next=next->nextlock;
805 if (next!=nil) {
806 /*found one; delete it */
807 trail->nextlock=next->nextlock;
808 Locks=dummy.nextlock;
809 next->delta->lockedby=nil; /* reset locked-by */
810 return next->delta;
811 } else {
812 if (!((StrictLocks==false) && (getuid() == RCSstat.st_uid))) {
813 error("no lock set by %s for revision %s",who,num);
814 return nil;
815 } else {
816 return delta;
823 char * getdate()
824 /* Function: returns a pointer to the current date in the form
825 * YY.MM.DD.hh.mm.ss\0
828 long clock;
829 struct tm * tm;
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);
836 return buffer;
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);
854 return nil;
856 while (expandline(unexfile,exfile,delta,false,false)); /*expand*/
857 ffclose(unexfile);ffclose(exfile);
858 return targetfname;
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.
871 { register int c;
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*/
884 result=false;
885 } else {
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');
892 if (result==false) {
893 if (quietflag) {
894 warn("checkin aborted since %s was not changed; %s %sdeleted.",
895 workfilename,workfilename,keepworkingfile?"not ":"");
896 } else {
897 diagnose("checkin aborted; %s %sdeleted.",
898 workfilename,keepworkingfile?"not ":"");
900 if (!keepworkingfile) VOID unlink(workfilename);
902 return result;
908 /* --------------------- G E T L O G M S G --------------------------------*/
909 extern int stdinread; /* is >0 if redirected stdin has been read once. */
912 char * getlogmsg()
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";
928 char response;
929 int cin;
930 register char c, old1, old2, * tp;
932 if (msg) return msg;
934 if ((olddeltanum==nil)&&
935 ((cmpnum(newdelnum,"1.1")==0)||(cmpnum(newdelnum,"1.0")==0))) {
936 return initiallog;
938 if (keepflag) {
939 /* generate std. log message */
940 VOID sprintf(logmsg, "checked in with -k by %s at %s.\n",caller,getdate());
941 return(logmsg);
943 if (logyet) {
944 /*previous log available*/
945 if (!ttystdin()) return logmsg; /* reuse if stdin is not a terminal*/
946 /* otherwise ask */
947 clearerr(stdin); /* reset EOF ptr */
948 VOID fputs("reuse log message of previous file? [yn](y): ",stderr);
949 cin=getchar();
950 response=cin;
951 while (!(cin==EOF || cin=='\n')) cin=getchar();/*skip to end of line*/
952 if (response=='\n'||response=='y'||response=='Y')
953 return logmsg;
954 else
955 logmsg[0]='\0'; /*kill existing log message */
958 /* now read string from stdin */
959 if (ttystdin()) {
960 VOID fputs("enter log message:\n(terminate with ^D or single '.')\n>> ",stderr);
961 } else { /* redirected stdin */
962 if (stdinread>0)
963 faterror("Can't reread redirected stdin for log message; use -m");
964 stdinread++;
967 tp=logmsg; old1='\n'; old2=' ';
968 if (feof(stdin))
969 clearerr(stdin);
970 for (;;) {
971 cin=getchar();
972 if (cin==EOF) {
973 if(ttystdin()) {
974 VOID printf("\n");
975 clearerr(stdin);
977 if ((tp==logmsg)||(*(tp-1)!='\n')) *tp++ = '\n'; /* append newline */
978 *tp = '\0'; /*terminate*/
979 break;
981 if (cin=='\n' && old1=='.' && old2=='\n') {
982 *(tp-1) = '\0'; /*kill last period */
983 break;
985 if (tp>=logmsg+logsize-2) { /* overflow */
986 if (!ttystdin()) {
987 warn("log message truncated to %d characters",logsize);
988 logmsg[logsize-2]='\n';logmsg[logsize-1]='\0';
989 return logmsg;
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 */
995 continue;
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*/
1000 } /* end for */
1002 /* now check whether the log message is not empty */
1003 tp=logmsg;
1004 while ((c= *tp++)==' '||c=='\t'||c=='\n'||c=='\f');
1005 if (*tp=='\0') {
1006 logyet=false;
1007 return emptylog;
1008 } else {
1009 logyet=true;
1010 return logmsg;