Sync usage with man page.
[netbsd-mini2440.git] / usr.bin / rcs / src / co.c
blobef011891e476326f9cec27d9e73bc6ab2f8cd4fb
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 checkout operation
29 #ifndef lint
30 static char rcsid[]=
31 "$Header: /pub/NetBSD/misc/repositories/cvsroot/src/usr.bin/rcs/src/Attic/co.c,v 1.1 1993/03/21 09:58:06 cgd Exp $ Purdue CS";
32 #endif
33 /*****************************************************************************
34 * check out revisions from RCS files
35 *****************************************************************************
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
43 * Revision 4.6 88/11/08 12:02:31 narten
44 * changes from eggert@sm.unisys.com (Paul Eggert)
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
50 * Revision 4.5 87/12/18 11:35:40 narten
51 * lint cleanups (from Guy Harris)
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
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)
61 * Revision 1.2 87/03/27 14:21:38 jenkins
62 * Port to suns
64 * Revision 1.1 84/01/23 14:49:58 kcs
65 * Initial revision
67 * Revision 4.2 83/12/05 13:39:48 wft
68 * made rewriteflag external.
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();
78 * Revision 3.7 83/02/15 15:27:07 wft
79 * Added call to fastcopy() to copy remainder of RCS file.
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.
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.
89 * Revision 3.4 82/12/04 18:40:00 wft
90 * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
91 * Fixed printing of "done".
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().
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.
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.
118 #include "rcsbase.h"
119 #include "time.h"
120 #include <sys/types.h>
121 #include <sys/stat.h>
123 #ifndef lint
124 static char rcsbaseid[] = RCSBASE;
125 #endif
126 static char co[] = CO;
127 static char merge[] = MERGE;
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 */
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 */
154 char * date, * rev, * state, * author, * join;
155 char finaldate[datelength];
157 int forceflag, lockflag, unlockflag, tostdout;
158 char * caller; /* caller's login; */
159 extern quietflag;
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 */
165 char * joinlist[joinlength]; /* pointers to revisions to be joined */
166 int lastjoin; /* index of last element in joinlist */
168 main (argc, argv)
169 int argc;
170 char * argv[];
172 int killock; /* indicates whether a lock is removed*/
173 char * cmdusage;
174 struct tm parseddate, *ftm;
175 char * rawdate;
176 long unixtime;
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 = "";
186 while (--argc,++argv, argc>=1 && ((*argv)[0] == '-')) {
187 switch ((*argv)[1]) {
189 case 'r':
190 revno: if ((*argv)[2]!='\0') {
191 if (rev!=nil) warn("Redefinition of revision number");
192 rev = (*argv)+2;
194 break;
196 case 'f':
197 forceflag=true;
198 goto revno;
200 case 'l':
201 lockflag=true;
202 if (unlockflag) {
203 warn("-l has precedence over -u");
204 unlockflag=false;
206 goto revno;
208 case 'u':
209 unlockflag=true;
210 if (lockflag) {
211 warn("-l has precedence over -u");
212 unlockflag=false;
214 goto revno;
216 case 'p':
217 tostdout=true;
218 goto revno;
220 case 'q':
221 quietflag=true;
222 goto revno;
224 case 'd':
225 if ((*argv)[2]!='\0') {
226 if (date!=nil) warn("Redefinition of -d option");
227 rawdate=(*argv)+2;
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;
240 case 'j':
241 if ((*argv)[2]!='\0'){
242 if (join!=nil)warn("Redefinition of -j option");
243 join = (*argv)+2;
245 break;
247 case 's':
248 if ((*argv)[2]!='\0'){
249 if (state!=nil)warn("Redefinition of -s option");
250 state = (*argv)+2;
252 break;
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;
261 default:
262 faterror("unknown option: %s\n%s", *argv,cmdusage);
265 } /* end of option processing */
267 if (argc<1) faterror("No input file\n%s",cmdusage);
269 /* now handle all filenames */
270 do {
271 rewriteflag=false;
272 finptr=frewrite=NULL;
273 neworkfilename=nil;
275 if (!pairfilenames(argc,argv,true,tostdout)) continue;
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.
282 diagnose("%s --> %s", RCSfilename,tostdout?"stdout":workfilename);
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 */
290 gettree(); /* reads in the delta tree */
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;
315 if (unlockflag) {
316 if((killock=rmlock(caller,targetdelta))== -1)
317 continue;
318 } else {
319 killock=0;
322 if (join && !preparejoin()) continue;
324 diagnose("revision %s%s",targetdelta->num,
325 lockflag?" (locked)":
326 unlockflag?" (unlocked)":"");
328 /* remove old working file if necessary */
329 if (!tostdout)
330 if (!rmworkfile()) continue;
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;
339 putadmin(frewrite);
340 puttree(Head,frewrite);
341 VOID fprintf(frewrite, "\n\n%s%c",Kdesc,nextc);
342 rewriteflag=true;
345 /* skip description */
346 getdesc(false); /* don't echo*/
348 if (!(neworkfilename=buildrevision(gendeltas,targetdelta,
349 tostdout?(join!=nil?"/tmp/":(char *)nil):workfilename,true)))
350 continue;
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;
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();
370 # ifdef SNOOPFILE
371 logcommand("co",targetdelta,gendeltas,caller);
372 # endif
374 if (join) {
375 rmsema(); /* kill semaphore file so other co's can proceed */
376 if (!buildjoin(neworkfilename)) continue;
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;
384 neworkfilename[0]= '\0'; /*avoid re-deletion by cleanup()*/
387 if (!tostdout)
388 if (chmod(workfilename, WORKMODE(RCSstat.st_mode))<0)
389 warn("Can't adjust mode of %s",workfilename);
392 if (!tostdout) diagnose("done");
393 } while (cleanup(),
394 ++argv, --argc >=1);
396 exit(nerror!=0);
398 } /* end of main (co) */
401 /*****************************************************************
402 * The following routines are auxiliary routines
403 *****************************************************************/
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.
414 int response, c; /* holds user response to queries */
416 if (haveworkstat< 0) /* File doesn't exist; set by pairfilenames*/
417 return (true); /* No problem */
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;
429 } else {
430 error("writable %s exists; checkout aborted.",workfilename);
431 return false;
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;
439 return true;
443 creatempty()
444 /* Function: creates an empty working file.
445 * First, removes an existing working file with rmworkfile().
448 int fdesc; /* file descriptor */
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;
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.
469 { register struct lock * next, * trail;
470 char * num;
471 struct lock dummy;
472 int whomatch, nummatch;
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;
486 trail=next;
487 next=next->nextlock;
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*/
501 /*****************************************************************
502 * The rest of the routines are for handling joins
503 *****************************************************************/
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.
516 register char c;
517 register int length;
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++;
526 *tp= '\0';
527 return sp;
532 int preparejoin()
533 /* Function: Parses a join list pointed to by join and places pointers to the
534 * revision numbers into joinlist.
537 struct hshentry * * joindeltas;
538 struct hshentry * tmpdelta;
539 register char * j;
540 char symbolrev[revlength],numrev[revlength];
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);
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;
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;
588 if (lastjoin<1) {
589 error("empty join");
590 return false;
591 } else return true;
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().
603 char commarg[revlength+3];
604 char subs[revlength];
605 char * rev2, * rev3;
606 int i;
608 rev2=mktempfile("/tmp/",JOINFIL2);
609 rev3=mktempfile("/tmp/",JOINFIL3);
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;
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;
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;
634 i=i+2;
636 return true;