|
|
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 checkout operation
28: */
29: #ifndef lint
30: static char rcsid[]=
31: "$Header: /usr/src/local/bin/rcs/src/RCS/co.c,v 4.7 89/05/01 15:11:41 narten Exp $ Purdue CS";
32: #endif
33: /*****************************************************************************
34: * check out revisions from RCS files
35: *****************************************************************************
36: */
37:
38:
39: /* $Log: co.c,v $
40: * Revision 4.7 89/05/01 15:11:41 narten
41: * changed copyright header to reflect current distribution rules
42: *
43: * Revision 4.6 88/11/08 12:02:31 narten
44: * changes from [email protected] (Paul Eggert)
45: *
46: * Revision 4.6 88/08/09 19:12:15 eggert
47: * Fix "co -d" core dump; rawdate wasn't always initialized.
48: * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint
49: *
50: * Revision 4.5 87/12/18 11:35:40 narten
51: * lint cleanups (from Guy Harris)
52: *
53: * Revision 4.4 87/10/18 10:20:53 narten
54: * Updating version numbers changes relative to 1.1, are actually
55: * relative to 4.2
56: *
57: * Revision 1.3 87/09/24 13:58:30 narten
58: * Sources now pass through lint (if you ignore printf/sprintf/fprintf
59: * warnings)
60: *
61: * Revision 1.2 87/03/27 14:21:38 jenkins
62: * Port to suns
63: *
64: * Revision 1.1 84/01/23 14:49:58 kcs
65: * Initial revision
66: *
67: * Revision 4.2 83/12/05 13:39:48 wft
68: * made rewriteflag external.
69: *
70: * Revision 4.1 83/05/10 16:52:55 wft
71: * Added option -u and -f.
72: * Added handling of default branch.
73: * Replaced getpwuid() with getcaller().
74: * Removed calls to stat(); now done by pairfilenames().
75: * Changed and renamed rmoldfile() to rmworkfile().
76: * Replaced catchints() calls with restoreints(), unlink()--link() with rename();
77: *
78: * Revision 3.7 83/02/15 15:27:07 wft
79: * Added call to fastcopy() to copy remainder of RCS file.
80: *
81: * Revision 3.6 83/01/15 14:37:50 wft
82: * Added ignoring of interrupts while RCS file is renamed; this avoids
83: * deletion of RCS files during the unlink/link window.
84: *
85: * Revision 3.5 82/12/08 21:40:11 wft
86: * changed processing of -d to use DATEFORM; removed actual from
87: * call to preparejoin; re-fixed printing of done at the end.
88: *
89: * Revision 3.4 82/12/04 18:40:00 wft
90: * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
91: * Fixed printing of "done".
92: *
93: * Revision 3.3 82/11/28 22:23:11 wft
94: * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
95: * %02d with %.2d, mode generation for working file with WORKMODE.
96: * Fixed nil printing. Fixed -j combined with -l and -p, and exit
97: * for non-existing revisions in preparejoin().
98: *
99: * Revision 3.2 82/10/18 20:47:21 wft
100: * Mode of working file is now maintained even for co -l, but write permission
101: * is removed.
102: * The working file inherits its mode from the RCS file, plus write permission
103: * for the owner. The write permission is not given if locking is strict and
104: * co does not lock.
105: * An existing working file without write permission is deleted automatically.
106: * Otherwise, co asks (empty answer: abort co).
107: * Call to getfullRCSname() added, check for write error added, call
108: * for getlogin() fixed.
109: *
110: * Revision 3.1 82/10/13 16:01:30 wft
111: * fixed type of variables receiving from getc() (char -> int).
112: * removed unused variables.
113: */
114:
115:
116:
117:
118: #include "rcsbase.h"
119: #include "time.h"
120: #include <sys/types.h>
121: #include <sys/stat.h>
122:
123: #ifndef lint
124: static char rcsbaseid[] = RCSBASE;
125: #endif
126: static char co[] = CO;
127: static char merge[] = MERGE;
128:
129: extern FILE * fopen();
130: extern int rename();
131: extern char * getcaller(); /*get login of caller */
132: extern struct hshentry * genrevs(); /*generate delta numbers */
133: extern char * getancestor();
134: extern int nextc; /*next input character */
135: extern int nerror; /*counter for errors */
136: extern char Kdesc[]; /*keyword for description */
137: extern char * buildrevision(); /*constructs desired revision */
138: extern int buildjoin(); /*join several revisions */
139: extern char * mktempfile(); /*temporary file name generator */
140: extern struct hshentry * findlock();/*find (and delete) a lock */
141: extern struct lock * addlock(); /*add a new lock */
142: extern long maketime(); /*convert parsed time to unix time. */
143: extern struct tm * localtime(); /*convert unixtime into a tm-structure */
144: extern FILE * finptr; /* RCS input file */
145: extern FILE * frewrite; /* new RCS file */
146: extern int rewriteflag; /* indicates whether input should be */
147: /* echoed to frewrite */
148:
149: char * newRCSfilename, * neworkfilename;
150: char * RCSfilename, * workfilename;
151: extern struct stat RCSstat, workstat; /* file status of RCS and work file */
152: extern int haveRCSstat, haveworkstat;/* status indicators */
153:
154: char * date, * rev, * state, * author, * join;
155: char finaldate[datelength];
156:
157: int forceflag, lockflag, unlockflag, tostdout;
158: char * caller; /* caller's login; */
159: extern quietflag;
160:
161: char numericrev[revlength]; /* holds expanded revision number */
162: struct hshentry * gendeltas[hshsize]; /* stores deltas to be generated */
163: struct hshentry * targetdelta; /* final delta to be generated */
164:
165: char * joinlist[joinlength]; /* pointers to revisions to be joined */
166: int lastjoin; /* index of last element in joinlist */
167:
168: main (argc, argv)
169: int argc;
170: char * argv[];
171: {
172: int killock; /* indicates whether a lock is removed*/
173: char * cmdusage;
174: struct tm parseddate, *ftm;
175: char * rawdate;
176: long unixtime;
177:
178: catchints();
179: cmdid = "co";
180: cmdusage = "command format:\nco -f[rev] -l[rev] -p[rev] -q[rev] -r[rev] -ddate -sstate -w[login] -jjoinlist file ...";
181: date = rev = state = author = join = nil;
182: forceflag = lockflag = unlockflag = tostdout = quietflag = false;
183: caller=getcaller();
184: rawdate = "";
185:
186: while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
187: switch ((*argv)[1]) {
188:
189: case 'r':
190: revno: if ((*argv)[2]!='\0') {
191: if (rev!=nil) warn("Redefinition of revision number");
192: rev = (*argv)+2;
193: }
194: break;
195:
196: case 'f':
197: forceflag=true;
198: goto revno;
199:
200: case 'l':
201: lockflag=true;
202: if (unlockflag) {
203: warn("-l has precedence over -u");
204: unlockflag=false;
205: }
206: goto revno;
207:
208: case 'u':
209: unlockflag=true;
210: if (lockflag) {
211: warn("-l has precedence over -u");
212: unlockflag=false;
213: }
214: goto revno;
215:
216: case 'p':
217: tostdout=true;
218: goto revno;
219:
220: case 'q':
221: quietflag=true;
222: goto revno;
223:
224: case 'd':
225: if ((*argv)[2]!='\0') {
226: if (date!=nil) warn("Redefinition of -d option");
227: rawdate=(*argv)+2;
228: }
229: /* process date/time */
230: if (partime(rawdate,&parseddate)==0)
231: faterror("Can't parse date/time: %s",rawdate);
232: if ((unixtime=maketime(&parseddate))== 0L)
233: faterror("Inconsistent date/time: %s",rawdate);
234: ftm=localtime(&unixtime);
235: VOID sprintf(finaldate,DATEFORM,
236: ftm->tm_year,ftm->tm_mon+1,ftm->tm_mday,ftm->tm_hour,ftm->tm_min,ftm->tm_sec);
237: date=finaldate;
238: break;
239:
240: case 'j':
241: if ((*argv)[2]!='\0'){
242: if (join!=nil)warn("Redefinition of -j option");
243: join = (*argv)+2;
244: }
245: break;
246:
247: case 's':
248: if ((*argv)[2]!='\0'){
249: if (state!=nil)warn("Redefinition of -s option");
250: state = (*argv)+2;
251: }
252: break;
253:
254: case 'w':
255: if (author!=nil)warn("Redefinition of -w option");
256: if ((*argv)[2]!='\0')
257: author = (*argv)+2;
258: else author = caller;
259: break;
260:
261: default:
262: faterror("unknown option: %s\n%s", *argv,cmdusage);
263:
264: };
265: } /* end of option processing */
266:
267: if (argc<1) faterror("No input file\n%s",cmdusage);
268:
269: /* now handle all filenames */
270: do {
271: rewriteflag=false;
272: finptr=frewrite=NULL;
273: neworkfilename=nil;
274:
275: if (!pairfilenames(argc,argv,true,tostdout)) continue;
276:
277: /* now RCSfilename contains the name of the RCS file, and finptr
278: * the file descriptor. If tostdout is false, workfilename contains
279: * the name of the working file, otherwise undefined (not nil!).
280: * Also, RCSstat, workstat, and haveworkstat have been set.
281: */
282: diagnose("%s --> %s", RCSfilename,tostdout?"stdout":workfilename);
283:
284:
285: if (!tostdout && !trydiraccess(workfilename)) continue; /* give up */
286: if ((lockflag||unlockflag) && !checkaccesslist(caller)) continue; /* give up */
287: if (!trysema(RCSfilename,lockflag||unlockflag)) continue; /* give up */
288:
289:
290: gettree(); /* reads in the delta tree */
291:
292: if (Head==nil) {
293: /* no revisions; create empty file */
294: diagnose("no revisions present; generating empty revision 0.0");
295: if (!tostdout)
296: if (!creatempty()) continue;
297: /* Can't reserve a delta, so don't call addlock */
298: } else {
299: if (rev!=nil) {
300: /* expand symbolic revision number */
301: if (!expandsym(rev,numericrev))
302: continue;
303: } elsif (unlockflag && (targetdelta=findlock(caller,false))!=nil) {
304: VOID strcpy(numericrev,targetdelta->num);
305: } elsif (Dbranch!=nil) {
306: VOID strcpy(numericrev,Dbranch->num);
307: } else numericrev[0]='\0'; /* empty */
308: /* get numbers of deltas to be generated */
309: if (!(targetdelta=genrevs(numericrev,date,author,state,gendeltas)))
310: continue;
311: /* check reservations */
312: if (lockflag && !addlock(targetdelta,caller))
313: continue;
314:
315: if (unlockflag) {
316: if((killock=rmlock(caller,targetdelta))== -1)
317: continue;
318: } else {
319: killock=0;
320: }
321:
322: if (join && !preparejoin()) continue;
323:
324: diagnose("revision %s%s",targetdelta->num,
325: lockflag?" (locked)":
326: unlockflag?" (unlocked)":"");
327:
328: /* remove old working file if necessary */
329: if (!tostdout)
330: if (!rmworkfile()) continue;
331:
332: /* prepare for rewriting the RCS file */
333: if (lockflag||(killock==1)) {
334: newRCSfilename=mktempfile(RCSfilename,NEWRCSFILE);
335: if ((frewrite=fopen(newRCSfilename, "w"))==NULL) {
336: error("Can't open file %s",newRCSfilename);
337: continue;
338: }
339: putadmin(frewrite);
340: puttree(Head,frewrite);
341: VOID fprintf(frewrite, "\n\n%s%c",Kdesc,nextc);
342: rewriteflag=true;
343: }
344:
345: /* skip description */
346: getdesc(false); /* don't echo*/
347:
348: if (!(neworkfilename=buildrevision(gendeltas,targetdelta,
349: tostdout?(join!=nil?"/tmp/":(char *)nil):workfilename,true)))
350: continue;
351:
352: if ((lockflag||killock==1)&&nerror==0) {
353: /* rewrite the rest of the RCSfile */
354: fastcopy(finptr,frewrite);
355: ffclose(frewrite); frewrite=NULL;
356: ignoreints();
357: if (rename(newRCSfilename,RCSfilename)<0) {
358: error("Can't rewrite %s; saved in: %s",
359: RCSfilename, newRCSfilename);
360: newRCSfilename[0]='\0'; /* avoid deletion*/
361: restoreints();
362: break;
363: }
364: newRCSfilename[0]='\0'; /* avoid re-deletion by cleanup()*/
365: if (chmod(RCSfilename,RCSstat.st_mode & ~0222)<0)
366: warn("Can't preserve mode of %s",RCSfilename);
367: restoreints();
368: }
369:
370: # ifdef SNOOPFILE
371: logcommand("co",targetdelta,gendeltas,caller);
372: # endif
373:
374: if (join) {
375: rmsema(); /* kill semaphore file so other co's can proceed */
376: if (!buildjoin(neworkfilename)) continue;
377: }
378: if (!tostdout) {
379: if (rename(neworkfilename,workfilename) <0) {
380: error("Can't create %s; see %s",workfilename,neworkfilename);
381: neworkfilename[0]= '\0'; /*avoid deletion*/
382: continue;
383: }
384: neworkfilename[0]= '\0'; /*avoid re-deletion by cleanup()*/
385: }
386: }
387: if (!tostdout)
388: if (chmod(workfilename, WORKMODE(RCSstat.st_mode))<0)
389: warn("Can't adjust mode of %s",workfilename);
390:
391:
392: if (!tostdout) diagnose("done");
393: } while (cleanup(),
394: ++argv, --argc >=1);
395:
396: exit(nerror!=0);
397:
398: } /* end of main (co) */
399:
400:
401: /*****************************************************************
402: * The following routines are auxiliary routines
403: *****************************************************************/
404:
405: int rmworkfile()
406: /* Function: unlinks workfilename, if it exists, under the following conditions:
407: * If it is read-only, workfilename is unlinked.
408: * Otherwise (file writable):
409: * if !quietmode asks the user whether to really delete it (default: fail);
410: * otherwise failure.
411: * Returns false on failure to unlink, true otherwise.
412: */
413: {
414: int response, c; /* holds user response to queries */
415:
416: if (haveworkstat< 0) /* File doesn't exist; set by pairfilenames*/
417: return (true); /* No problem */
418:
419: if ((workstat.st_mode & 0222)&&!forceflag) { /* File is writable */
420: if (!quietflag) {
421: VOID fprintf(stderr,"writable %s exists; overwrite? [ny](n): ",workfilename);
422: /* must be stderr in case of IO redirect */
423: c=response=getchar();
424: while (!(c==EOF || c=='\n')) c=getchar(); /*skip rest*/
425: if (!(response=='y'||response=='Y')) {
426: warn("checkout aborted.");
427: return false;
428: }
429: } else {
430: error("writable %s exists; checkout aborted.",workfilename);
431: return false;
432: }
433: }
434: /* now unlink: either not writable, forceflag, or permission given */
435: if (unlink(workfilename) != 0) { /* Remove failed */
436: error("Can't unlink %s",workfilename);
437: return false;
438: }
439: return true;
440: }
441:
442:
443: creatempty()
444: /* Function: creates an empty working file.
445: * First, removes an existing working file with rmworkfile().
446: */
447: {
448: int fdesc; /* file descriptor */
449:
450: if (!rmworkfile()) return false;
451: fdesc=creat(workfilename,0);
452: if (fdesc < 0) {
453: faterror("Cannot create %s",workfilename);
454: return false;
455: } else {
456: VOID close(fdesc); /* empty file */
457: return true;
458: }
459: }
460:
461:
462: int rmlock(who,delta)
463: char * who; struct hshentry * delta;
464: /* Function: removes the lock held by who on delta.
465: * Returns -1 if someone else holds the lock,
466: * 0 if there is no lock on delta,
467: * and 1 if a lock was found and removed.
468: */
469: { register struct lock * next, * trail;
470: char * num;
471: struct lock dummy;
472: int whomatch, nummatch;
473:
474: num=delta->num;
475: dummy.nextlock=next=Locks;
476: trail = &dummy;
477: while (next!=nil) {
478: whomatch=strcmp(who,next->login);
479: nummatch=strcmp(num,next->delta->num);
480: if ((whomatch==0) && (nummatch==0)) break;
481: /*found a lock on delta by who*/
482: if ((whomatch!=0)&&(nummatch==0)) {
483: error("revision %s locked by %s; use co -r or rcs -u",num,next->login);
484: return -1;
485: }
486: trail=next;
487: next=next->nextlock;
488: }
489: if (next!=nil) {
490: /*found one; delete it */
491: trail->nextlock=next->nextlock;
492: Locks=dummy.nextlock;
493: next->delta->lockedby=nil; /* reset locked-by */
494: return 1; /*success*/
495: } else return 0; /*no lock on delta*/
496: }
497:
498:
499:
500:
501: /*****************************************************************
502: * The rest of the routines are for handling joins
503: *****************************************************************/
504:
505: char * getrev(sp, tp, buffsize)
506: register char * sp, *tp; int buffsize;
507: /* Function: copies a symbolic revision number from sp to tp,
508: * appends a '\0', and returns a pointer to the character following
509: * the revision number; returns nil if the revision number is more than
510: * buffsize characters long.
511: * The revision number is terminated by space, tab, comma, colon,
512: * semicolon, newline, or '\0'.
513: * used for parsing the -j option.
514: */
515: {
516: register char c;
517: register int length;
518:
519: length = 0;
520: while (((c= *sp)!=' ')&&(c!='\t')&&(c!='\n')&&(c!=':')&&(c!=',')
521: &&(c!=';')&&(c!='\0')) {
522: if (length>=buffsize) return false;
523: *tp++= *sp++;
524: length++;
525: }
526: *tp= '\0';
527: return sp;
528: }
529:
530:
531:
532: int preparejoin()
533: /* Function: Parses a join list pointed to by join and places pointers to the
534: * revision numbers into joinlist.
535: */
536: {
537: struct hshentry * * joindeltas;
538: struct hshentry * tmpdelta;
539: register char * j;
540: char symbolrev[revlength],numrev[revlength];
541:
542: joindeltas = (struct hshentry * *)talloc(hshsize*sizeof(struct hshentry *));
543: j=join;
544: lastjoin= -1;
545: for (;;) {
546: while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
547: if (*j=='\0') break;
548: if (lastjoin>=joinlength-2) {
549: error("too many joins");
550: return(false);
551: }
552: if(!(j=getrev(j,symbolrev,revlength))) return false;
553: if (!expandsym(symbolrev,numrev)) return false;
554: tmpdelta=genrevs(numrev,(char *)nil,(char *)nil,(char *)nil,(struct hshentry * *)joindeltas);
555: if (tmpdelta==nil)
556: return false;
557: else joinlist[++lastjoin]=tmpdelta->num;
558: while ((*j==' ') || (*j=='\t')) j++;
559: if (*j == ':') {
560: j++;
561: while((*j==' ') || (*j=='\t')) j++;
562: if (*j!='\0') {
563: if(!(j=getrev(j,symbolrev,revlength))) return false;
564: if (!expandsym(symbolrev,numrev)) return false;
565: tmpdelta=genrevs(numrev,(char *)nil,(char *)nil,(char *)nil, (struct hshentry * *) joindeltas);
566: if (tmpdelta==nil)
567: return false;
568: else joinlist[++lastjoin]=tmpdelta->num;
569: } else {
570: error("join pair incomplete");
571: return false;
572: }
573: } else {
574: if (lastjoin==0) { /* first pair */
575: /* common ancestor missing */
576: joinlist[1]=joinlist[0];
577: lastjoin=1;
578: /*derive common ancestor*/
579: joinlist[0]=talloc(revlength);
580: if (!getancestor(targetdelta->num,joinlist[1],joinlist[0]))
581: return false;
582: } else {
583: error("join pair incomplete");
584: return false;
585: }
586: }
587: }
588: if (lastjoin<1) {
589: error("empty join");
590: return false;
591: } else return true;
592: }
593:
594:
595:
596: buildjoin(initialfile)
597: char * initialfile;
598: /* Function: merge pairs of elements in joinlist into initialfile
599: * If tostdout==true, copy result to stdout.
600: * All unlinking of initialfile, rev2, and rev3 should be done by cleanup().
601: */
602: {
603: char commarg[revlength+3];
604: char subs[revlength];
605: char * rev2, * rev3;
606: int i;
607:
608: rev2=mktempfile("/tmp/",JOINFIL2);
609: rev3=mktempfile("/tmp/",JOINFIL3);
610:
611: i=0;
612: while (i<lastjoin) {
613: /*prepare marker for merge*/
614: if (i==0)
615: VOID strcpy(subs,targetdelta->num);
616: else VOID sprintf(subs, "merge%d",i/2);
617: diagnose("revision %s",joinlist[i]);
618: VOID sprintf(commarg,"-p%s",joinlist[i]);
619: if (run((char*)nil,rev2, co,commarg,"-q",RCSfilename,(char*)nil)) {
620: nerror++;return false;
621: }
622: diagnose("revision %s",joinlist[i+1]);
623: VOID sprintf(commarg,"-p%s",joinlist[i+1]);
624: if (run((char *)nil,rev3, co,commarg,"-q",RCSfilename,(char*)nil)) {
625: nerror++; return false;
626: }
627: diagnose("merging...");
628: if (
629: (i+2)>=lastjoin && tostdout
630: ? run((char*)nil,(char*)nil, merge,"-p",initialfile,rev2,rev3,subs,joinlist[i+1],(char*)nil)
631: : run((char*)nil,(char*)nil, merge, initialfile,rev2,rev3,subs,joinlist[i+1],(char*)nil)) {
632: nerror++; return false;
633: }
634: i=i+2;
635: }
636: return true;
637: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.