4 * Copyright (C) 2006 Peter Backes <rtc@gmx.de>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * flex cvsclone.l && gcc -Wall -O2 lex.yy.c -o cvsclone
26 * *-------------------------------------------------------------------*
27 * | Applying this tool to sourceforge.net or savannah.gnu.org is |
28 * | neither necessary nor recommended: With $1 being the project, you |
30 * | rsync -av rsync://$1.cvs.sourceforge.net/cvsroot/$1/ $1 |
32 * | rsync -av rsync://cvs.sv.gnu.org/sources/$1 $1 |
33 * | respectively (try also 'web' instead of 'sources'). |
34 * *-------------------------------------------------------------------*
36 * cvsclone -d :pserver:anonymous@cvs.example.org:/var/lib/cvs module
40 * Utility to clone cvs repositories over the cvspserver interface. Works
41 * for anonymous access.
45 * - reads $HOME/.cvspass
47 * - can clone corrupt repositories: writes ,v files directly, does not
48 * need rcs. (For example, ccvs module has archives that go backwards
53 * - can't enable compression.
55 * - reading cvs password from $HOME/.cvspass uses CVSROOT in a
58 * - rlog format is ambiguous. If the separators it uses are found inside
59 * log messages, possibly followed by lines similar to what rlog
60 * outputs, things can go wrong horribly.
62 * - rcs 5.x rlog format does not contain the comment leader. It is
63 * guessed according to the extension as rcs and CVS do.
65 * - uses normal diff format since this is the easiest one that works.
66 * diff --rcs is problematic, since files without newline at the
67 * last line are not output correctly. The major drawback about this
68 * is that deleted lines are transfered while they don't need to be.
69 * even rdiff has major problems with lines that contain \0, because
72 * - does not work incrementally. That would be much more work if
73 * updating the trunk since the most recent revision had to be
74 * reconstructed. Also, the whole history probably had to be transfered
75 * again, with all log messages.
77 * - Horrible complexity. A file with n deltas takes O(n^2) to transfer.
79 * - Makes the cvs server really work hard, taking up all processor time.
80 * It should really not be used on public cvs servers, especially
81 * not on a regular basis. Perhaps it is useful for salvaging
82 * archive files from projects where only access to anonymous cvs
86 * Patches and comments are welcome.
91 #include <sys/types.h>
92 #include <sys/socket.h>
106 #ifdef _IO_getc_unlocked
108 #define getc _IO_getc_unlocked
111 %s HDR0 HDR1 DESC ACCL0 TAGL0 TAGL1 LCKL0 LCKL1 REV0 REV1 REV2 ATR0 ATR1
112 %s REV3 REV4 RLST XREV FEND SRV0 SRVA SRVB SRV1 SRV2 SRV3 SRV4 SRV5 SRV6 PWF0
114 %option noyywrap nounput
117 idchar [^$,.:;@ \b\t\v\f\r\n]
119 id {num}?{idchar}({idchar}|{num})*
120 sym [0-9]?{idchar}({idchar}|[0-9])*
121 xid ({idchar}|{special}|{ws})+
122 yid ({idchar}|{special})+
124 date1 [0-9]+(-[0-9]{2}){2}\ [0-9]{2}(:[0-9]{2}){2}\ [-+][0-9]{4}
125 date2 [0-9]+(\/[0-9]{2}){2}\ [0-9]{2}(:[0-9]{2}){2}
140 keyword substitution: kv
141 total revisions: ; selected\ revisions:
145 file() : trunk(Head) tree(Head)
148 | adelta(p) trunk(p->next)
151 | tree(root->next) forest(root->branches)
154 | forest(broot->nextbranch) abranch(broot) tree(broot)
157 | abranch(root->next) adelta(root)
161 "----------------------------\n"
162 "revision %s" [ "\tlocked by: %s;" ] "\n"
164 "date: %s; author: %s; state: %s; lines: +%ld -%ld"
168 [ "; mergepoint:" (" %s;")+ ]
171 [ "branches:" (" %s;")+ "\n" ]
172 [ "included:" (" %s;")+ ]
173 [ "excluded:" (" %s;")+ ]
174 [ "ignored:" (" %s;")+ ] "\n" // '\n' if one of them is there
175 [ "source: %s" [ "\t%s" ] "\n" ]
180 char *getstr(unsigned long n, char *s)
189 char *author, *state, *comment, *commitid, *kopt;
196 char *server, /* originating server */
197 *onum; /* original number */
199 struct rev *next, *branch, *sub;
210 char *source, *workfile;
211 union revref head, branch;
216 struct rpair *lckl, *lckt, *tagl, *tagt;
217 char **accl, **acct; /* access list, access top */
218 struct rev *revl, *revt;
223 rfile.source = rfile.workfile = rfile.head.num
224 = rfile.branch.num = rfile.ksub = rfile.leader
225 = rfile.descr = NULL;
226 rfile.strict = rfile.tot = rfile.sel = 0;
227 rfile.lckl = rfile.lckt = rfile.tagl = rfile.tagt = NULL;
228 rfile.revl = rfile.revt = NULL;
229 rfile.accl = rfile.acct = NULL;
232 void addaccl(char *s)
235 rfile.acct = rfile.accl = malloc(16 * sizeof *rfile.accl);
236 else if (!((rfile.acct - rfile.accl) % 16)) {
237 unsigned long n = rfile.acct - rfile.accl;
238 rfile.accl = realloc(rfile.accl, (n+16) * sizeof *rfile.accl);
239 rfile.acct = rfile.accl + n;
247 rfile.tagt = rfile.tagl = malloc(16 * sizeof *rfile.tagl);
248 else if (!((rfile.tagt - rfile.tagl) % 16)) {
249 unsigned long n = rfile.tagt - rfile.tagl;
250 rfile.tagl = realloc(rfile.tagl, (n+16) * sizeof *rfile.tagl);
251 rfile.tagt = rfile.tagl + n;
254 rfile.tagt++->item = s;
259 rfile.lckt = rfile.lckl = malloc(16 * sizeof *rfile.lckl);
260 else if (!((rfile.lckt - rfile.lckl) % 16)) {
261 unsigned long n = rfile.lckt - rfile.lckl;
262 rfile.lckl = realloc(rfile.lckl, (n+16) * sizeof *rfile.lckl);
263 rfile.lckt = rfile.lckl + n;
266 rfile.lckt++->item = s;
268 void setdate(unsigned long n, char *s, char *t)
272 sscanf(s, "%d%c%d%c%d %d:%d:%d", &tm.tm_year, &d, &tm.tm_mon, &d,
273 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
279 tm.tm_sec += -z / 100 * 3600 + -z % 100 * 60;
281 tm.tm_sec -= z / 100 * 3600 + z % 100 * 60;
283 rfile.revt->date = timegm(&tm);
285 printf("setdate: %.*s, %.5s --> %s", (int)n, s, t,
286 ctime(&rfile.revt->date));
293 rfile.revt = rfile.revl;
296 assert(rfile.revl && rfile.revt < rfile.revl + rfile.sel);
297 /* assert(rfile.revt - rfile.revl < rfile.n) */
299 rfile.revt->author = rfile.revt->state
300 = rfile.revt->comment = rfile.revt->commitid
301 = rfile.revt->kopt = rfile.revt->server
302 = rfile.revt->onum = rfile.revt->log = NULL;
303 rfile.revt->next = rfile.revt->branch = rfile.revt->sub = NULL;
304 rfile.revt->ladd = rfile.revt->ldel = 0;
312 rfile.revl = malloc(n * sizeof *rfile.revl);
314 char *cvsroot, *cvspass, *cvsuser, *cvshost, *cvsdir;
315 int chkroot(char *s, size_t le, int s1)
317 size_t l = strlen(cvsroot);
322 fprintf(stderr, "cvsroot=%s, s=%s, s1=%d\n", cvsroot, s, s1);
324 if (*s != ':' || !(x = strchr(s + 1, ':'))
325 || !(x = strchr(x + 1, ':'))
326 || strncmp(cvsroot, s, x - s + 1)
327 || !isdigit(*(x + 1)))
330 fprintf(stderr, "survived\n");
332 y = cvsroot + (x - s) + 1;
333 while (isdigit(*++x))
335 if (strncmp(y, x, l - (y - cvsroot))
336 || x[l - (y - cvsroot)] != ' ')
338 l += x - s - (y - cvsroot);
340 fprintf(stderr, "l=%u(%d, %u), s=%s, cvsroot=%s, x=%s, y=%s\n",
341 l, x - s - (y - cvsroot), le,
344 } else if (strncmp(cvsroot, s, l) || s[l] != ' ')
347 cvsroot = strncpy(realloc(cvsroot, le + 1), s, le);
348 cvsroot[l] = cvsroot[le+1] = '\0';
349 cvspass = cvsroot + l + 1;
353 void rcsfwrite(char *s, size_t l, FILE *stream)
355 for (; l--; putc(*s++, stream))
359 void rcsrang(int d0, int d1, int a0, int a1)
361 fprintf(rcsfiop, "d%d %d\na%d %d\n", d0, d1-d0+1, d1, a1-a0+1);
363 void rcsrang2(int d0, int d1, int a)
365 fprintf(rcsfiop, "d%d %d\n", d0, d1-d0+1);
367 void rcsrang3(int d, int a0, int a1)
369 fprintf(rcsfiop, "a%d %d\n", d, a1-a0+1);
376 return queue = malloc(queuel = l);
378 queue = realloc(queue, queuel);
379 return queue + queuel - l;
381 void begin(char *s, size_t l)
383 char *p = s + 10, *fi, *fi2;
384 size_t l0 = strlen(cvsdir);
386 if (strncmp(cvsdir, p, l0) || p[l0] != '/') {
387 fprintf(stderr, "path mismatch.\n");
391 while ((fi2 = strchr(fi, '/'))) {
393 mkdir(p + l0 + 1, 0777);
397 strcpy(getq(strlen(p + l0 + 1) + 1), p + l0 + 1);
398 if (!access(p + l0 + 1, R_OK)) {
400 snprintf(buffer, sizeof(buffer), "%s.old", p + l0 + 1);
401 rename(p + l0 + 1, buffer);
403 if (!(rcsfiop = fopen(p + l0 + 1, "w")))
406 fputs("\n", rcsfiop);
407 fwrite(s, 1, l, rcsfiop);
413 <HDR0>RCS\ file:\ {xid}\n rfile.source = getstr(yyleng-11, yytext+10);
414 <HDR0>Working\ file:\ {xid}\n rfile.workfile = getstr(yyleng-15, yytext+14);
415 <HDR0>head:\ {num}\n rfile.head.num = getstr(yyleng-7, yytext+6);
416 <HDR0>head:\n rfile.head.num = NULL;
417 <HDR0>branch:\n rfile.branch.num = NULL;
418 <HDR0>branch:\ {num}\n rfile.branch.num = getstr(yyleng-9, yytext+8);
419 <HDR0>locks:\ strict\n BEGIN LCKL0; rfile.strict = 1;
420 <HDR0>locks:\n BEGIN LCKL0; rfile.strict = 0;
421 <HDR0>access\ list:\n BEGIN ACCL0;
422 <HDR0>symbolic\ names:\n BEGIN TAGL0;
423 <HDR0>keyword\ substitution:\ .*\n rfile.ksub = getstr(yyleng-23, yytext+22);
424 <HDR0>comment\ leader:\ \".*\"\n rfile.leader = getstr(yyleng-19, yytext+17);
425 <HDR0>total\ revisions:\ [0-9]+;\t BEGIN HDR1; rfile.tot = atoi(yytext+17);
426 <HDR1>selected\ revisions:\ [0-9]+\n BEGIN HDR0; initrev(atoi(yytext+20));
427 <HDR0>description:\n BEGIN DESC;
429 <DESC>={77}\n BEGIN FEND; *yytext = '\0'; if (!rfile.descr) rfile.descr = yytext;
430 <DESC>-{28}\n BEGIN REV0; *yytext = '\0'; if (!rfile.descr) rfile.descr = yytext;
431 <DESC>.*\n if (!rfile.descr) rfile.descr = yytext;
432 <ACCL0>\t{id}\n addaccl(getstr(yyleng-2, yytext+1));
433 <ACCL0>""/[^\t] BEGIN HDR0;
434 <TAGL0>\t{sym}:\ BEGIN TAGL1; addtag(getstr(yyleng-3, yytext+1));
435 <TAGL0>""/[^\t] BEGIN HDR0;
436 <TAGL1>{num}\n BEGIN TAGL0; rfile.tagt[-1].rev.num = getstr(yyleng-1, yytext);
437 <LCKL0>\t{id}:\ BEGIN LCKL1; addlck(getstr(yyleng-3, yytext+1));
438 <LCKL0>""/[^\t] BEGIN HDR0;
439 <LCKL1>{num}\n BEGIN LCKL0; rfile.lckt[-1].rev.num = getstr(yyleng-1, yytext);
440 <REV0>revision\ {num}\n BEGIN REV2; addrev(getstr(yyleng-10, yytext+9));
441 <REV0>revision\ {num}\t BEGIN REV1; addrev(getstr(yyleng-10, yytext+9));
442 <REV1>locked\ by:\ {id};\n BEGIN REV2; /* getstr(yyleng-13, yytext+11); */
443 <REV2>date:\ {date1};\ \ BEGIN ATR0; setdate(yyleng-15, yytext+6, yytext+yyleng-8);
444 <REV2>date:\ {date2};\ \ BEGIN ATR0; setdate(yyleng-9, yytext+6, NULL);
445 <ATR0>author:\ {id};\ \ rfile.revt->author = getstr(yyleng-11, yytext+8);
446 <ATR0>state:\ {id};\ \ rfile.revt->state = getstr(yyleng-10, yytext+7);
447 <ATR0>state:\ {id};\n BEGIN REV3; rfile.revt->state = getstr(yyleng-9, yytext+7);
448 <ATR0>lines:\ \+[0-9]+\ BEGIN ATR1; rfile.revt->ladd = atoi(yytext+8);
449 <ATR1>-[0-9]+;?\n BEGIN REV3; rfile.revt->ldel = atoi(yytext+1);
450 <ATR1>-[0-9]+;\ \ BEGIN ATR0; rfile.revt->ldel = atoi(yytext+1);
451 <ATR0>kopt:\ [^;\n]+;\n BEGIN REV3; rfile.revt->kopt = getstr(yyleng-8, yytext+6);
452 <ATR0>kopt:\ [^;\n]+;\ \ rfile.revt->kopt = getstr(yyleng-9, yytext+6);
453 <ATR0>commitid:\ [0-9a-zA-Z]{16};\ \ rfile.revt->commitid = getstr(16, yytext+10);
454 <ATR0>commitid:\ [0-9a-zA-Z]{16};\n BEGIN REV3; rfile.revt->commitid = getstr(16, yytext+10);
455 <ATR0>mergepoint:\ {num};\n BEGIN REV3; /* getstr(yyleng-14, yytext+12); */
456 <REV3>branches:/.*\n BEGIN RLST;
457 <REV3,REV4>={77}\n BEGIN FEND; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext; if (rfile.revt) rfile.revt++;
458 <REV3,REV4>-{28}\n BEGIN REV0; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext;
459 <REV3>.*\n BEGIN REV4; assert(!rfile.revt->log); rfile.revt->log = yytext;
460 <RLST>\ \ {num}; addrl(getstr(yyleng-3, yytext+2));
462 <REV4>The\ changelog\ prior\ to\ shifting.*\n BEGIN REVSKIP; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext;
463 <REVSKIP>={77}\n BEGIN REV0; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext;
464 <REVSKIP>.*\n *yytext = '\0'; /* do nothing */
465 <REV4>.*\n if (!rfile.revt->log) rfile.revt->log = yytext;
466 <FEND><<EOF>> return EOF;
468 <SRV0>M\ \n BEGIN SRVA;
469 <SRVA>M\ RCS\ file:\ {xid}\n BEGIN SRVB; begin(yytext+2, yyleng-2);
470 <SRVB>M\ ={77}\n BEGIN SRV0; fwrite(yytext+2, 1, yyleng-2, rcsfiop); fclose(rcsfiop);
471 <SRVB>M\ .*\n fwrite(yytext+2, 1, yyleng-2, rcsfiop);
472 <SRV0,SRV1,SRV2,SRV3,SRV4,SRV5,SRV6>E\ .*\n fwrite(yytext+2, 1, yyleng-2, stderr);
473 <SRV0>I\ LOVE\ YOU\n fprintf(stderr, "%s", yytext);
474 <SRV0>error\ [^ \n]*\ .*\n return 1;
476 <SRV0><<EOF>> return EOF;
477 <SRV1>M\ <\ .*\n BEGIN SRV2; rcsfwrite(yytext+4, yyleng-5, rcsfiop);
478 <SRV2>M\ <\ .*\n yytext[3] = '\n'; rcsfwrite(yytext+3, yyleng-4, rcsfiop);
479 <SRV2>M\ \\\ .*\n BEGIN SRV3;
480 <SRV2,SRV5>ok\n putc('\n', rcsfiop); return 0;
481 <SRV2,SRV5>error\ [^ \n]*\ .*\n putc('\n', rcsfiop); return 1;
482 <SRV3,SRV1,SRV4,SRV6>ok\n return 0;
483 <SRV3,SRV1,SRV4,SRV6>error\ [^ \n]*\ .*\n return 1;
484 <SRV4>M\ [0-9]+,[0-9]+c[0-9]+,[0-9]+\n rcsrang(atoi(yytext+2), atoi(strchr(yytext, ',')+1), atoi(strchr(yytext, 'c')+1), atoi(strrchr(yytext, ',')+1));
485 <SRV4>M\ [0-9]+c[0-9]+,[0-9]+\n rcsrang(atoi(yytext+2), atoi(yytext+2), atoi(strchr(yytext, 'c')+1), atoi(strrchr(yytext, ',')+1));
486 <SRV4>M\ [0-9]+,[0-9]+c[0-9]+\n rcsrang(atoi(yytext+2), atoi(strchr(yytext, ',')+1), atoi(strchr(yytext, 'c')+1), atoi(strchr(yytext, 'c')+1));
487 <SRV4>M\ [0-9]+c[0-9]+\n rcsrang(atoi(yytext+2), atoi(yytext+2), atoi(strchr(yytext, 'c')+1), atoi(strchr(yytext, 'c')+1));
488 <SRV4>M\ [0-9]+,[0-9]+d[0-9]+\n rcsrang2(atoi(yytext+2), atoi(strchr(yytext, ',')+1), atoi(strchr(yytext, 'd')+1));
489 <SRV4>M\ [0-9]+d[0-9]+\n rcsrang2(atoi(yytext+2), atoi(yytext+2), atoi(strchr(yytext, 'd')+1));
490 <SRV4>M\ [0-9]+a[0-9]+,[0-9]+\n rcsrang3(atoi(yytext+2), atoi(strchr(yytext, 'a')+1), atoi(strchr(yytext, ',')+1));
491 <SRV4>M\ [0-9]+a[0-9]+\n rcsrang3(atoi(yytext+2), atoi(strrchr(yytext, 'a')+1), atoi(strchr(yytext, 'a')+1));
492 <SRV4>M\ >\ .*\n BEGIN SRV5; rcsfwrite(yytext+4, yyleng-5, rcsfiop);
493 <SRV5>M\ >\ .*\n yytext[3] = '\n'; rcsfwrite(yytext+3, yyleng-4, rcsfiop);
494 <SRV5>""/M\ [0-9] BEGIN SRV4; putc('\n', rcsfiop);
495 <SRV5>M\ \\\ .*\n BEGIN SRV6;
496 <SRV1,SRV4>M\ RCS\ file:.*\n |
497 <SRV1,SRV4>M\ ========.*\n |
498 <SRV1,SRV4>M\ retrieving.*\n |
499 <SRV1,SRV4>M\ diff.*\n |
500 <SRV1,SRV4>M\ Index:.*\n |
501 <SRV1>M\ [0-9]+(,[0-9]+)?d[0-9]+\n |
503 <SRV4>M\ (<\ .*|---)\n /* ignore */
504 <SRV5>([^M\n]|M([^ \n]|\ [^0-9\n])).*\n? |
506 <SRV0,SRV1,SRV2,SRV3,SRV4,SRV6>.*\n? fwrite(yytext, 1, yyleng, stderr);
508 <PWF0>\/1\ [^ \n]+\ .*\n if (chkroot(yytext+3, yyleng-4, 1)) return 0;
509 <PWF0>[^ \n]+\ .*\n if (chkroot(yytext, yyleng-1, 0)) return 0;
510 <PWF0>.*\n? /* ignore unknown lines */
511 <PWF0><<EOF>> return EOF;
515 <TAGL0,LCKL0>\t[^:\n]*:?\n? |
516 <TAGL0,LCKL0>\t[^:\n]*:[^ ].*\n? |
517 <TAGL0,LCKL0>\t[^:\n]*:\ |
519 <REV0>[^\t\n]*[\t\n]? |
520 <REV2>[^;\n]*(""|;\ ?) |
521 <ATR0>[^l\n][^;\n]* |
522 <ATR0>[^l\n][^;\n]*;\ ? |
523 <ATR0>(\n|l[^ \n]*) |
524 <ATR0>lines:\ [^ \n]* |
527 <HDR1,DESC,TAGL1>.*\n? |
528 <LCKL1,REV1,REV3>.*\n? |
529 <REV4,XREV,SRVA>.*\n? |
531 (.|\n) printf("Unmached: <%.*s>\n", yyleng, yytext);
534 static YY_BUFFER_STATE yybuf;
535 /* Read rlog to buffer */
536 size_t rlread(char *ptr, size_t nmemb, FILE *stream)
541 register int c = getc(stream);
554 } else if (state < 78) {
564 void *ftobuf(FILE *stream, unsigned long *s)
566 unsigned long siz = 0;
569 buf = realloc(buf, siz + BUFSIZ + 2);
570 siz += fread(buf + siz, 1, BUFSIZ, stream);
571 /*siz += rlread(buf + siz, BUFSIZ, stream);*/
572 } while (siz && !(siz % BUFSIZ));
579 return realloc(buf, siz);
582 char *strfix(char *s)
584 unsigned long l = strlen(s);
591 FILE *srv, *rlog_input;
596 struct hostent *host;
597 struct servent *serv;
598 struct sockaddr_in sin;
601 char *cvsport = NULL;
602 if (strncmp(cvsroot, ":pserver:", 9)
603 || !(cvsdir = strchr(cvsuser = cvsroot + 9, ':'))
604 || !(cvshost = strchr(cvsuser, '@')) || cvshost > cvsdir)
606 *cvshost++ = *cvsdir++ = '\0';
607 if (isdigit(*cvsdir)) {
609 while (isdigit(*++cvsdir))
613 memset(&sin, '\0', sizeof sin);
615 sin.sin_port = htons(atoi(cvsport));
616 else if (!(serv = getservbyname("cvspserver", "tcp")))
619 sin.sin_port = serv->s_port;
620 if (!(host = gethostbyname(cvshost))
621 || (fd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
623 sin.sin_family = AF_INET;
624 memcpy(&sin.sin_addr, host->h_addr, host->h_length);
625 if (connect(fd, (struct sockaddr *)&sin, sizeof sin) == -1) {
629 return fdopen(fd, "w+");
634 char *getpar(char *s)
636 char *p = NULL, *q = NULL;
645 struct rev *range(struct rev *begin)
648 if (begin >= rfile.revt)
651 bre = strrchr(brb, '.');
652 if (!bre || bre == strchr(brb, '.'))
656 /*fprintf(stderr, "brb: %s, n: %u\n", brb, bre - brb);*/
657 while (begin < rfile.revt
658 && (bre - brb ? !strncmp(begin->num, brb, bre - brb)
659 && !strchr(begin->num + (bre - brb), '.')
660 : strchr(begin->num, '.') == strrchr(begin->num, '.')))
661 /* works fine even if no '.' present */
674 1.1--1.1.1.1--1.1.1.1.1.1 - -
686 * | | -> | / <1.1.2.1*
688 * | | - | | | /1.1.1.2
689 ^ | -> - | - | | |1.1.1.1*
691 * | | | | | | / /1.1.1.2.1.2
692 ^ | | -> | | | \ \1.1.1.2.1.1*
694 * | | | | | / /1.1.1.1.1.2
695 ^ | -> | \ \ \ \1.1.1.1.1.1*
701 int prefix(const char *rev, const char *subrev)
703 size_t l = strlen(rev);
704 return !strncmp(rev, subrev, l) && subrev[l] == '.';
707 /* parse forest belonging to revision x */
708 struct rev *forest(struct rev *x, struct rev **b)
710 struct rev *z = NULL;
712 struct rev *branch(struct rev **b);
713 struct rev *y = branch(b);
716 } while (*b < rfile.revt && prefix(x->num, (*b)->num));
720 struct rev *branch(struct rev **b)
722 struct rev *e = range(*b), *x, *y = *b;
724 for (x = *b; x < e - 1; x++)
726 /* As for the trunk, for each branch revision, a forest can follow.
727 * However here the forests in decreasing order match the order
728 * of the branch revisions.
730 while (e < rfile.revt) {
731 while (y <= x && !prefix(y->num, e->num))
735 y->branch = forest(y, &e);
743 struct rev *trunk(struct rev *b)
745 struct rev *e = b ? range(b) : NULL, *x, *y = e;
748 for (x = b; x < e - 1; x++)
751 /* For each trunk revision, a forest can follow.
752 * The forests are in increasing order, in contrast to the
753 * trunk revisions, which are in decreasing order.
755 while (e < rfile.revt) {
756 /* search the trunk revision this forest belongs to */
757 while (y > b && !prefix((--y)->num, e->num))
759 assert(y > b || prefix(y->num, e->num));
760 /* parse the forest, set branch of the trunk revision */
761 y->branch = forest(y, &e);
767 int ndeltas, nfiles, nbranches;
770 void rcsfputs(const char *s, FILE *stream)
772 for (putc('@', stream); *s; putc(*s++, stream))
777 void puttree(struct rev *x, FILE *stream)
780 void putforest(struct rev *x, FILE *stream);
781 for (y = x; y; y = y->next) {
783 tm = gmtime(&y->date);
784 fprintf(stream, "%s\n", y->num);
785 fprintf(stream, "date\t%d.%02d.%02d.%02d.%02d.%02d;"
786 "\tauthor %s;\tstate %s;\n",
787 tm->tm_year > 99 ? tm->tm_year + 1900 : tm->tm_year,
788 tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min,
789 tm->tm_sec, y->author, y->state);
790 fprintf(stream, "branches");
791 for (z = y->branch; z; z = z->sub)
792 fprintf(stream, "\n\t%s", z->num);
794 fprintf(stream, ";\n");
795 fprintf(stream, "next\t%s;\n", y->next ? y->next->num : "");
796 fprintf(stream, "\n");
798 putforest(x, stream);
800 void putforest(struct rev *x, FILE *stream)
805 putforest(x->next, stream);
806 for (z = x->branch; z; z = z->sub)
812 if (que == queue + queuel)
814 fcntl(fileno(srv), F_SETFL, O_NONBLOCK);
815 if ((s = write(fileno(srv), que, queue + queuel - que)) != -1)
817 fcntl(fileno(srv), F_SETFL, 0L);
820 void puttree2(struct rev *z, struct rev *x)
823 for (y = x; y; y = y->next) {
824 void putfor2(struct rev *z, struct rev *x);
827 for (z = y; z && !strcmp(z->state, "dead"); z = z->next)
830 sprintf(getq(10*5+5+3+2+3+2+strlen(z->num)
831 + (fi2 - strrchr(fi, '/')) - 1),
838 (int)(fi2 - strrchr(fi, '/') - 1),
839 strrchr(fi, '/') + 1);
840 queue[queuel-1] = '\n';
842 /* file was initially added on branch */
844 } else if (strcmp(y->state, "dead") && z != y) {
845 sprintf(getq(10*6+5+3+2+2+3+2+strlen(z->num)
847 + (fi2 - strrchr(fi, '/'))),
854 "diff", z->num, y->num,
855 (int)(fi2 - strrchr(fi, '/') - 1),
856 strrchr(fi, '/') + 1);
857 queue[queuel-1] = '\n';
860 putfor2(z, y->branch);
863 void putfor2(struct rev *z, struct rev *x)
870 void puttree3(struct rev *z, struct rev *x)
873 for (y = x; y; y = y->next) {
874 void putfor3(struct rev *z, struct rev *x);
875 fprintf(stderr, "%s ", y->num);
878 fprintf(rcsfiop, "\n\n%s\nlog\n", y->num);
879 rcsfputs(y->log, rcsfiop);
880 fprintf(rcsfiop, "\ntext\n@");
883 for (z = y; z && !strcmp(z->state, "dead"); z = z->next)
890 /* file was initially added on branch */
892 } else if (strcmp(y->state, "dead") && z != y) {
898 fprintf(rcsfiop, "@\n");
899 putfor3(z, y->branch);
902 void putfor3(struct rev *z, struct rev *x)
911 /* table of comment leader pairs, merged from RCS and CVS */
912 static const struct clpair {
913 char *suffix, *comlead;
915 { "a" , "-- " }, /* Ada */
919 { "asm" , ";; " }, /* assembler (MS-DOS) */
920 { "bas" , "' " }, /* Visual Basic code */
921 { "bat" , ":: " }, /* batch (MS-DOS) */
922 { "body", "-- " }, /* Ada */
923 { "c" , " * " }, /* C */
924 { "c++" , "// " }, /* C++ in all its infinite guises */
928 { "cl" , ";;; "}, /* Common Lisp */
929 { "cmd" , ":: " }, /* command (OS/2) */
930 { "cmf" , "c " }, /* CM Fortran */
931 { "cs" , " * " }, /* C* */
932 { "csh" , "# " }, /* shell */
933 { "dlg" , " * " }, /* MS Windows dialog file */
934 { "e" , "# " }, /* efl */
935 { "epsf", "% " }, /* encapsulated postscript */
936 { "epsi", "% " }, /* encapsulated postscript */
937 { "el" , "; " }, /* Emacs Lisp */
938 { "f" , "c " }, /* Fortran */
940 { "frm" , "' " }, /* Visual Basic form */
941 { "h" , " * " }, /* C-header */
943 { "hpp" , "// " }, /* C++ header */
945 { "in" , "# " }, /* for Makefile.in */
946 { "l" , " * " }, /* lex (NOTE: franzlisp disagrees) */
947 { "lisp", ";;; "}, /* Lucid Lisp */
948 { "lsp" , ";; " }, /* Microsoft Lisp */
949 { "m" , "// " }, /* Objective C */
950 { "mac" , ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
951 { "mak" , "# " }, /* makefile, e.g. Visual C++ */
952 { "me" , ".\\\" "}, /* troff -me */
953 { "ml" , "; " }, /* mocklisp */
954 { "mm" , ".\\\" "}, /* troff -mm */
955 { "ms" , ".\\\" "}, /* troff -ms */
956 { "man" , ".\\\" "}, /* man-macros t/nroff */
957 { "1" , ".\\\" "}, /* feeble attempt at man pages... */
966 { "p" , " * " }, /* Pascal */
968 { "pl" , "# " }, /* perl (conflict with Prolog) */
969 { "ps" , "% " }, /* PostScript */
970 { "psw" , "% " }, /* postscript wrap */
971 { "pswm", "% " }, /* postscript wrap */
972 { "r" , "# " }, /* ratfor */
973 { "rc" , " * " }, /* Microsoft Windows resource file */
974 { "red" , "% " }, /* psl/rlisp */
976 { "s" , "! " }, /* assembler */
979 { "s" , "| " }, /* assembler */
982 { "s" , "/ " }, /* assembler */
985 { "s" , "# " }, /* assembler */
988 { "s" , "# " }, /* assembler */
989 { "S" , "# " }, /* Macro assembler */
991 { "sh" , "# " }, /* shell */
992 { "sl" , "% " }, /* psl */
993 { "spec", "-- " }, /* Ada */
994 { "sty" , "% " }, /* LaTeX style */
995 { "tex" , "% " }, /* TeX */
996 { "y" , " * " }, /* yacc */
997 { "ye" , " * " }, /* yacc-efl */
998 { "yr" , " * " }, /* yacc-ratfor */
999 { "" , "# " }, /* default for empty suffix */
1000 { 0 , "# " } /* default for unknown suffix; must be last */
1003 static int is_version_number(const char *line)
1005 if (*line < '0' || *line > '9')
1007 for (; *line; line++)
1008 if (*line != '\n' && *line != '.' &&
1009 (*line < '0' || *line > '9'))
1014 static int is_done(const char *filename)
1016 char buffer[1024], line[1024];
1018 int remaining = rfile.sel;
1020 snprintf(buffer, sizeof(buffer), "%s.old", filename);
1021 orig = fopen(buffer, "rb");
1025 if (!fgets(line, sizeof(line), orig) || strncmp(line, "head\t", 5)) {
1030 while (!feof(orig) && fgets(line, sizeof(line), orig)) {
1031 if (!strcmp(line, "desc\n"))
1033 if (is_version_number(line))
1039 rename(buffer, filename);
1045 static const char *current_file;
1046 static void unlink_current_file(int dummy)
1049 unlink(current_file);
1055 size_t l = strlen(cvsdir);
1056 char **i, suffix[6] = "";
1058 fprintf(stderr, "%s\n", rfile.source);
1059 fi = strrchr(rfile.source, '/');
1060 sprintf(getq(12 + (fi - rfile.source) + 1),
1061 "Directory .\n%.*s",
1062 (int)(fi - rfile.source), rfile.source);
1063 queue[queuel-1] = '\n';
1064 fi2 = strrchr(fi, ',');
1065 fi = strrchr(fi, '.');
1066 assert(fi2 && (!fi || fi < fi2));
1069 /* If the suffix length is greater than four characters,
1070 * it cannot match, since it copies five of them.
1072 strncpy(suffix, fi, fi2 - fi < sizeof suffix - 1
1073 ? fi2 - fi : sizeof suffix - 1);
1075 fi = rfile.source + l + 1;
1079 rcsfiop = fopen(fi, "w");
1081 fprintf(rcsfiop, "head\t%s;\n", rfile.head.num ? rfile.head.num : "");
1082 if (rfile.branch.num)
1083 fprintf(rcsfiop, "branch\t%s;\n", rfile.branch.num);
1084 fprintf(rcsfiop, "access");
1085 for (i = rfile.accl; i < rfile.acct; i++)
1086 fprintf(rcsfiop, "\n\t%s", *i);
1087 fprintf(rcsfiop, ";\n");
1088 fprintf(rcsfiop, "symbols");
1089 for (j = rfile.tagl; j < rfile.tagt; j++)
1090 fprintf(rcsfiop, "\n\t%s:%s", j->item, j->rev.num);
1091 fprintf(rcsfiop, ";\n");
1092 fprintf(rcsfiop, "locks");
1093 for (j = rfile.lckl; j < rfile.lckt; j++)
1094 fprintf(rcsfiop, "\n\t%s:%s", j->item, j->rev.num);
1095 fprintf(rcsfiop, ";%s\n", rfile.strict ? " strict;" : "");
1096 fprintf(rcsfiop, "comment\t");
1098 rcsfputs(rfile.leader, rcsfiop);
1100 const struct clpair *curr;
1101 for (curr = cltbl; curr->suffix; curr++)
1102 if (!strcmp(curr->suffix, suffix))
1104 rcsfputs(curr->comlead, rcsfiop);
1106 fprintf(rcsfiop, ";\n");
1107 if (rfile.ksub && strcmp(rfile.ksub, "kv")) {
1108 fprintf(rcsfiop, "expand\t");
1109 rcsfputs(rfile.ksub, rcsfiop);
1110 fprintf(rcsfiop, ";\n");
1112 fprintf(rcsfiop, "\n\n");
1115 puttree(rfile.revl, rcsfiop);
1116 fprintf(rcsfiop, "\ndesc\n");
1117 rcsfputs(rfile.descr, rcsfiop);
1118 fprintf(rcsfiop, "\n");
1119 puttree2(NULL, rfile.revl);
1121 puttree3(NULL, rfile.revl);
1125 fprintf(stderr, "\n");
1127 current_file = NULL;
1130 int main(int argc, char *argv[])
1133 char *buf, *passfile, *fn0, *fn1, *fn2;
1135 if (argc == 5 || argc == 3)
1136 if (!(rlog_input = fopen(argv[--argc], "rb"))) {
1137 fprintf(stderr, "Invalid rlog file\n");
1138 return EXIT_FAILURE;
1141 if (argc == 4 && !strcmp(argv[1], "-d")) {
1145 } else if (argc != 2) {
1146 fprintf(stderr, "Argument count.\n");
1147 return EXIT_FAILURE;
1149 cvsroot = getenv("CVSROOT");
1152 fprintf(stderr, "No CVSROOT.\n");
1153 return EXIT_FAILURE;
1155 cvsroot = strdup(cvsroot);
1156 passfile = (home = getenv("HOME"))
1157 ? strcat(strcpy(malloc(strlen(home)
1158 + sizeof "/.cvspass"), home), "/.cvspass")
1159 : strcpy(malloc(sizeof ".cvspass"), ".cvspass");
1161 if (!(yyin = fopen(passfile, "r")) || yylex() == EOF) {
1162 size_t l = strlen(cvsroot);
1163 cvsroot = realloc(cvsroot, l + 2);
1164 cvspass = strcpy(cvsroot + l + 1, "A");
1168 fprintf(stderr, "password file: %s\n"
1170 "pass: %.1s\n", passfile,
1173 signal(SIGINT, unlink_current_file);
1174 if (!(srv = srvopen())) {
1175 fprintf(stderr, "server connection failed or bad cvsroot\n");
1177 return EXIT_FAILURE;
1181 fprintf(srv, "BEGIN AUTH REQUEST\n"
1185 "END AUTH REQUEST\n", cvsdir, cvsuser, cvspass);
1186 fprintf(srv, "Root %s\n", cvsdir);
1187 /*fprintf(srv, "Argument micq/m4\n");*/
1188 fprintf(srv, "Argument %s\n", argv[1]);
1190 yyrestart(rlog_input);
1194 fprintf(srv, "rlog\n");
1198 yy_set_interactive(1);
1199 fprintf(stderr, "exit: %d\n", yylex());
1201 fn1 = queue + queuel;
1204 fn2 = fn0 = malloc(strlen(argv[1]) + 3);
1205 fn1 = strlen(argv[1]) + 3 + strcpy(fn0, argv[1]);
1208 while (fn0 < fn1 && (buf = ftobuf(rcsfiop = fopen(fn0, "r"), &siz))) {
1210 fn0 += strlen(fn0) + 1;
1211 buf[siz - 2] = YY_END_OF_BUFFER_CHAR;
1212 buf[siz - 1] = YY_END_OF_BUFFER_CHAR;
1213 /* fprintf(stderr, "<<%s>>\n", buf); */
1214 yybuf = YY_CURRENT_BUFFER;
1215 yy_scan_buffer(buf, siz);
1216 yy_set_interactive(0);
1220 assert(YY_CURRENT_BUFFER);
1221 yy_delete_buffer(YY_CURRENT_BUFFER);
1222 yy_switch_to_buffer(yybuf);
1237 fprintf(stderr, "%s clone successful: %d files, "
1238 "%d branches, %d deltas\n",
1239 argv[0], stats.nfiles, stats.nbranches, stats.ndeltas);
1243 return EXIT_SUCCESS;