|
|
1.1 root 1: /* Copyright (C) 1982, 1988, 1989 Walter Tichy
2: * All rights reserved.
3: *
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.
13: *
14: * Report all problems and direct all questions to:
15: * [email protected]
16: *
17:
18:
19:
20:
21:
22:
23:
24: */
25:
26: /*
27: * RCS checkin operation
28: */
29: #ifndef lint
30: static char rcsid[]=
31: "$Header: /usr/src/local/bin/rcs/src/RCS/ci.c,v 4.9 89/05/01 15:10:54 narten Exp $ Purdue CS";
32: #endif
33: /*******************************************************************
34: * check revisions into RCS files
35: *******************************************************************
36: */
37:
38:
39:
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
43: *
44: * Revision 4.8 88/11/08 13:38:23 narten
45: * changes from [email protected] (Super User)
46: * -d with no arguments uses the mod time of the file it is checking in
47: *
48: * Revision 4.7 88/11/08 10:59:04 narten
49: * changes from eggert
50: *
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()
55: *
56: * Revision 4.6 87/12/18 11:34:41 narten
57: * lint cleanups (from Guy Harris)
58: *
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
62: *
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)
66: *
67: * Revision 1.2 87/03/27 14:21:33 jenkins
68: * Port to suns
69: *
70: * Revision 1.1 84/01/23 14:49:54 kcs
71: * Initial revision
72: *
73: * Revision 4.3 83/12/15 12:28:54 wft
74: * ci -u and ci -l now set mode of working file properly.
75: *
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.
79: *
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.
90: *
91: * Revision 3.9.1.1 83/10/19 04:21:03 lepreau
92: * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
93: *
94: * Revision 3.9 83/02/15 15:25:44 wft
95: * 4.2 prerelease
96: *
97: * Revision 3.9 83/02/15 15:25:44 wft
98: * Added call to fastcopy() to copy remainder of RCS file.
99: *
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.
103: *
104: * Revision 3.7 82/12/10 16:09:20 wft
105: * Corrected checking of return code from diff.
106: *
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().
110: *
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.
114: *
115: * Revision 3.4 82/12/02 13:27:13 wft
116: * added option -k.
117: *
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().
125: *
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.
133: *
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.
138: */
139:
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"
147:
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 */
166:
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 */
175:
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 */
180:
181:
182: int copyflag; /* indicates whether a string should be copied into memory*/
183:
184: char * rev, * state, *msg;
185:
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 */
204:
205: main (argc, argv)
206: int argc;
207: char * argv[];
208: {
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;
217:
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;
227:
228: while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
229: switch ((*argv)[1]) {
230:
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;
236: }
237: break;
238:
239: case 'l':
240: keepworkingfile=lockflag=true;
241: goto revno;
242:
243: case 'u':
244: keepworkingfile=true; lockflag=false;
245: goto revno;
246:
247: case 'q':
248: quietflag=true;
249: goto revno;
250:
251: case 'f':
252: forceciflag=true;
253: goto revno;
254:
255: case 'k':
256: keepflag=true;
257: goto revno;
258:
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';
269: }
270: if (msg[msglen-1]!='\n') {
271: /*append linefeed*/
272: VOID strcpy(logmsg,msg);msg=logmsg;
273: msg[msglen] = '\n';
274: msg[++msglen]= '\0';
275: }
276: } else warn("Missing message for -m option");
277: break;
278:
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;
288:
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;
298:
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;
306:
307: case 't':
308: textflag=true;
309: if ((*argv)[2]!='\0'){
310: if (textfile!=nil)warn("Redefinition of -t option");
311: textfile = (*argv)+2;
312: }
313: break;
314:
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;
323: }
324: if ( (unixtime = maketime(&parseddate)) == 0L) {
325: faterror("Inconsistent date/time: %s",(*argv)+2);
326: break;
327: }
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;
334:
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;
342:
343:
344:
345:
346: default:
347: faterror("unknown option: %s\n%s", *argv,cmdusage);
348: };
349: } /* end processing of options */
350:
351: if (argc<1) faterror("No input file\n%s",cmdusage);
352:
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");
356: }
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;
364:
365: switch (pairfilenames(argc,argv,false,false)) {
366:
367: case -1: /* New RCS file */
368: initflag=true; rcsinitflag=false;
369: break;
370:
371: case 0: /* Error */
372: continue;
373:
374: case 1: /* Normal checkin with prev . RCS file */
375: initflag=false; rcsinitflag=(Head==nil);
376: }
377:
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.
383: */
384:
385: diagnose("%s <-- %s", RCSfilename,workfilename);
386:
387: if (access(workfilename,4)!=0) {
388: error("working file %s not readable or nonexistent",
389: workfilename);
390: continue;
391: }
392:
393: /*
394: * make sure workfile is a regular file.
395: */
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;
400: }
401:
402: /*
403: * if RCSfile doesn't exist, use mode from workfile, otherwise
404: * keep the one from the RCSfile.
405: */
406: if (! (initflag || rcsinitflag))
407: VOID fstat(fileno(finptr), &filestatus);
408:
409: if (!trydiraccess(RCSfilename)) continue; /* give up */
410: if (!initflag && !checkaccesslist(caller)) continue; /* give up */
411: if (!trysema(RCSfilename,true)) continue; /* give up */
412:
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;
419: }
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 */
427:
428: gettree(); /* reads in the delta tree.*/
429:
430: /* expand symbolic revision number */
431: if (!expandsym(rev,newdelnum)) continue;
432:
433: /* splice new delta into tree */
434: if (!addelta()) continue;
435:
436: if (initflag||rcsinitflag) {
437: diagnose("initial revision: %s",newdelnum);
438: } else diagnose("new revision: %s; previous revision: %s",
439: newdelnum,olddeltanum);
440:
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;
460: }
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);
464: }
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;
477: }
478:
479:
480: if (lockflag && !addlock(&newdelta,caller)) continue;
481: if (symbol && !addsymbol(&newdelta,symbol,symrebindflag)) continue;
482:
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;
488: }
489: putadmin(frewrite);
490: puttree(Head,frewrite);
491: putdesc(initflag,textflag,textfile,quietflag);
492:
493:
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;
530: }
531:
532: /* rewrite rest of RCS file */
533: fastcopy(finptr,frewrite);
534: ffclose(frewrite); frewrite=NULL;
535: }
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;
544: }
545: newRCSfilename[0]='\0'; /* avoid re-unlinking by cleanup()*/
546:
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);
551:
552: restoreints();
553: # ifdef SNOOPFILE
554: logcommand("ci",&newdelta,gendeltas,caller);
555: # endif
556:
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;
569: }
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();
574: }
575: diagnose("done");
576:
577: } while (cleanup(),
578: ++argv, --argc >=1);
579:
580: exit(nerror!=0);
581: /*NOTREACHED*/
582: } /* end of main (ci) */
583: /*****************************************************************/
584: /* the rest are auxiliary routines */
585:
586:
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.
592: */
593: {
594: register char * sp, * tp;
595: register int i;
596:
597: newdnumlength=countnumflds(newdelnum);
598:
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);
604: }
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;
615: }
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;
638: }
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;
646: }
647: if (Dbranch) {
648: VOID strcpy(newdelnum,Dbranch->num);
649: } else {
650: incnum(Head->num,newdelnum);
651: }
652: newdnumlength=countnumflds(newdelnum);
653: /* now fall into next statement */
654: }
655: }
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");
664: }
665: if (cmpnum(newdelnum,olddeltanum) <= 0) {
666: error("deltanumber %s too low; must be higher than %s",
667: newdelnum,Head->num);
668: return false;
669: }
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 */
681: }
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;
689: }
690: if (!addbranch(targetdelta,newdelnum)) return false;
691: }
692: return true;
693: }
694:
695:
696:
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.
706: */
707: {
708: struct branchhead * bhead, * btrail;
709: char branchnum[revlength];
710: int numlength, result, field;
711:
712: numlength = countnumflds(num);
713:
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;
723:
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;
742: }
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;
760: }
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 */
766: }
767: }
768: newbranch.hsh = &newdelta;
769: newdelta.next=nil;
770: return true;
771: }
772:
773:
774:
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.
783: */
784: {
785: register struct lock * next, * trail;
786: char * num;
787: struct lock dummy;
788: int whomatch, nummatch;
789:
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;
801: }
802: trail=next;
803: next=next->nextlock;
804: }
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;
817: }
818: }
819: }
820:
821:
822:
823: char * getdate()
824: /* Function: returns a pointer to the current date in the form
825: * YY.MM.DD.hh.mm.ss\0
826: */
827: {
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;
837: }
838:
839:
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.
846: */
847: { char * targetfname;
848: FILE * unexfile, *exfile;
849:
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;
855: }
856: while (expandline(unexfile,exfile,delta,false,false)); /*expand*/
857: ffclose(unexfile);ffclose(exfile);
858: return targetfname;
859: }
860:
861:
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.
870: */
871: { register int c;
872: int response, result;
873:
874: if (forceciflag) return true;
875:
876: if (!rcsfcmp(workfilename,unexfname,delta)) return true;
877: /* If files are different, must check them in. */
878:
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');
891: }
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 ":"");
899: }
900: if (!keepworkingfile) VOID unlink(workfilename);
901: }
902: return result;
903: }
904:
905:
906:
907:
908: /* --------------------- G E T L O G M S G --------------------------------*/
909: extern int stdinread; /* is >0 if redirected stdin has been read once. */
910:
911:
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.
923: */
924: {
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;
931:
932: if (msg) return msg;
933:
934: if ((olddeltanum==nil)&&
935: ((cmpnum(newdelnum,"1.1")==0)||(cmpnum(newdelnum,"1.0")==0))) {
936: return initiallog;
937: }
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);
942: }
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 */
956: }
957:
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++;
965: }
966:
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);
976: }
977: if ((tp==logmsg)||(*(tp-1)!='\n')) *tp++ = '\n'; /* append newline */
978: *tp = '\0'; /*terminate*/
979: break;
980: }
981: if (cin=='\n' && old1=='.' && old2=='\n') {
982: *(tp-1) = '\0'; /*kill last period */
983: break;
984: }
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;
990: }
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;
996: }
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 */
1001:
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;
1011: }
1012: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.