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]{15};\ \ rfile.revt->commitid = getstr(15, yytext+10);
454 <ATR0>commitid:\ [0-9a-zA-Z]{16};\ \ rfile.revt->commitid = getstr(16, yytext+10);
455 <ATR0>commitid:\ [0-9a-zA-Z]{15};\n BEGIN REV3; rfile.revt->commitid = getstr(15, yytext+10);
456 <ATR0>commitid:\ [0-9a-zA-Z]{16};\n BEGIN REV3; rfile.revt->commitid = getstr(16, yytext+10);
457 <ATR0>mergepoint:\ {num};\n BEGIN REV3; /* getstr(yyleng-14, yytext+12); */
458 <REV3>branches:/.*\n BEGIN RLST;
459 <REV3,REV4>={77}\n BEGIN FEND; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext; if (rfile.revt) rfile.revt++;
460 <REV3,REV4>-{28}\n BEGIN REV0; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext;
461 <REV3>.*\n BEGIN REV4; assert(!rfile.revt->log); rfile.revt->log = yytext;
462 <RLST>\ \ {num}; addrl(getstr(yyleng-3, yytext+2));
464 <REV4>The\ changelog\ prior\ to\ shifting.*\n BEGIN REVSKIP; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext;
465 <REVSKIP>={77}\n BEGIN REV0; *yytext = '\0'; if (!rfile.revt->log) rfile.revt->log = yytext;
466 <REVSKIP>.*\n *yytext = '\0'; /* do nothing */
467 <REV4>.*\n if (!rfile.revt->log) rfile.revt->log = yytext;
468 <FEND><<EOF>> return EOF;
470 <SRV0>M\ \n BEGIN SRVA;
471 <SRVA>M\ RCS\ file:\ {xid}\n BEGIN SRVB; begin(yytext+2, yyleng-2);
472 <SRVB>M\ ={77}\n BEGIN SRV0; fwrite(yytext+2, 1, yyleng-2, rcsfiop); fclose(rcsfiop);
473 <SRVB>M\ .*\n fwrite(yytext+2, 1, yyleng-2, rcsfiop);
474 <SRV0,SRV1,SRV2,SRV3,SRV4,SRV5,SRV6>E\ .*\n fwrite(yytext+2, 1, yyleng-2, stderr);
475 <SRV0>I\ LOVE\ YOU\n fprintf(stderr, "%s", yytext);
476 <SRV0>error\ [^ \n]*\ .*\n return 1;
478 <SRV0><<EOF>> return EOF;
479 <SRV1>M\ <\ .*\n BEGIN SRV2; rcsfwrite(yytext+4, yyleng-5, rcsfiop);
480 <SRV2>M\ <\ .*\n yytext[3] = '\n'; rcsfwrite(yytext+3, yyleng-4, rcsfiop);
481 <SRV2>M\ \\\ .*\n BEGIN SRV3;
482 <SRV2,SRV5>ok\n putc('\n', rcsfiop); return 0;
483 <SRV2,SRV5>error\ [^ \n]*\ .*\n putc('\n', rcsfiop); return 1;
484 <SRV3,SRV1,SRV4,SRV6>ok\n return 0;
485 <SRV3,SRV1,SRV4,SRV6>error\ [^ \n]*\ .*\n return 1;
486 <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));
487 <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));
488 <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));
489 <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));
490 <SRV4>M\ [0-9]+,[0-9]+d[0-9]+\n rcsrang2(atoi(yytext+2), atoi(strchr(yytext, ',')+1), atoi(strchr(yytext, 'd')+1));
491 <SRV4>M\ [0-9]+d[0-9]+\n rcsrang2(atoi(yytext+2), atoi(yytext+2), atoi(strchr(yytext, 'd')+1));
492 <SRV4>M\ [0-9]+a[0-9]+,[0-9]+\n rcsrang3(atoi(yytext+2), atoi(strchr(yytext, 'a')+1), atoi(strchr(yytext, ',')+1));
493 <SRV4>M\ [0-9]+a[0-9]+\n rcsrang3(atoi(yytext+2), atoi(strrchr(yytext, 'a')+1), atoi(strchr(yytext, 'a')+1));
494 <SRV4>M\ >\ .*\n BEGIN SRV5; rcsfwrite(yytext+4, yyleng-5, rcsfiop);
495 <SRV5>M\ >\ .*\n yytext[3] = '\n'; rcsfwrite(yytext+3, yyleng-4, rcsfiop);
496 <SRV5>""/M\ [0-9] BEGIN SRV4; putc('\n', rcsfiop);
497 <SRV5>M\ \\\ .*\n BEGIN SRV6;
498 <SRV1,SRV4>M\ RCS\ file:.*\n |
499 <SRV1,SRV4>M\ ========.*\n |
500 <SRV1,SRV4>M\ retrieving.*\n |
501 <SRV1,SRV4>M\ diff.*\n |
502 <SRV1,SRV4>M\ Index:.*\n |
503 <SRV1>M\ [0-9]+(,[0-9]+)?d[0-9]+\n |
505 <SRV4>M\ (<\ .*|---)\n /* ignore */
506 <SRV5>([^M\n]|M([^ \n]|\ [^0-9\n])).*\n? |
508 <SRV0,SRV1,SRV2,SRV3,SRV4,SRV6>.*\n? fwrite(yytext, 1, yyleng, stderr);
510 <PWF0>\/1\ [^ \n]+\ .*\n if (chkroot(yytext+3, yyleng-4, 1)) return 0;
511 <PWF0>[^ \n]+\ .*\n if (chkroot(yytext, yyleng-1, 0)) return 0;
512 <PWF0>.*\n? /* ignore unknown lines */
513 <PWF0><<EOF>> return EOF;
517 <TAGL0,LCKL0>\t[^:\n]*:?\n? |
518 <TAGL0,LCKL0>\t[^:\n]*:[^ ].*\n? |
519 <TAGL0,LCKL0>\t[^:\n]*:\ |
521 <REV0>[^\t\n]*[\t\n]? |
522 <REV2>[^;\n]*(""|;\ ?) |
523 <ATR0>[^l\n][^;\n]* |
524 <ATR0>[^l\n][^;\n]*;\ ? |
525 <ATR0>(\n|l[^ \n]*) |
526 <ATR0>lines:\ [^ \n]* |
529 <HDR1,DESC,TAGL1>.*\n? |
530 <LCKL1,REV1,REV3>.*\n? |
531 <REV4,XREV,SRVA>.*\n? |
533 (.|\n) printf("Unmached: <%.*s>\n", yyleng, yytext);
536 static YY_BUFFER_STATE yybuf;
537 /* Read rlog to buffer */
538 size_t rlread(char *ptr, size_t nmemb, FILE *stream)
543 register int c = getc(stream);
556 } else if (state < 78) {
566 void *ftobuf(FILE *stream, unsigned long *s)
568 unsigned long siz = 0;
571 buf = realloc(buf, siz + BUFSIZ + 2);
572 siz += fread(buf + siz, 1, BUFSIZ, stream);
573 /*siz += rlread(buf + siz, BUFSIZ, stream);*/
574 } while (siz && !(siz % BUFSIZ));
581 return realloc(buf, siz);
584 char *strfix(char *s)
586 unsigned long l = strlen(s);
593 FILE *srv, *rlog_input;
598 struct hostent *host;
599 struct servent *serv;
600 struct sockaddr_in sin;
603 char *cvsport = NULL;
604 if (strncmp(cvsroot, ":pserver:", 9)
605 || !(cvsdir = strchr(cvsuser = cvsroot + 9, ':'))
606 || !(cvshost = strchr(cvsuser, '@')) || cvshost > cvsdir)
608 *cvshost++ = *cvsdir++ = '\0';
609 if (isdigit(*cvsdir)) {
611 while (isdigit(*++cvsdir))
615 memset(&sin, '\0', sizeof sin);
617 sin.sin_port = htons(atoi(cvsport));
618 else if (!(serv = getservbyname("cvspserver", "tcp")))
621 sin.sin_port = serv->s_port;
622 if (!(host = gethostbyname(cvshost))
623 || (fd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
625 sin.sin_family = AF_INET;
626 memcpy(&sin.sin_addr, host->h_addr, host->h_length);
627 if (connect(fd, (struct sockaddr *)&sin, sizeof sin) == -1) {
631 return fdopen(fd, "w+");
636 char *getpar(char *s)
638 char *p = NULL, *q = NULL;
647 struct rev *range(struct rev *begin)
650 if (begin >= rfile.revt)
653 bre = strrchr(brb, '.');
654 if (!bre || bre == strchr(brb, '.'))
658 /*fprintf(stderr, "brb: %s, n: %u\n", brb, bre - brb);*/
659 while (begin < rfile.revt
660 && ((bre - brb) ? !strncmp(begin->num, brb, bre - brb)
661 && !strchr(begin->num + (bre - brb), '.')
662 : strchr(begin->num, '.') == strrchr(begin->num, '.')))
663 /* works fine even if no '.' present */
676 1.1--1.1.1.1--1.1.1.1.1.1 - -
688 * | | -> | / <1.1.2.1*
690 * | | - | | | /1.1.1.2
691 ^ | -> - | - | | |1.1.1.1*
693 * | | | | | | / /1.1.1.2.1.2
694 ^ | | -> | | | \ \1.1.1.2.1.1*
696 * | | | | | / /1.1.1.1.1.2
697 ^ | -> | \ \ \ \1.1.1.1.1.1*
703 int prefix(const char *rev, const char *subrev)
705 size_t l = strlen(rev);
706 return !strncmp(rev, subrev, l) && subrev[l] == '.';
709 /* parse forest belonging to revision x */
710 struct rev *forest(struct rev *x, struct rev **b)
712 struct rev *z = NULL;
714 struct rev *branch(struct rev **b);
715 struct rev *y = branch(b);
718 } while (*b < rfile.revt && prefix(x->num, (*b)->num));
722 struct rev *branch(struct rev **b)
724 struct rev *e = range(*b), *x, *y = *b;
726 for (x = *b; x < e - 1; x++)
728 /* As for the trunk, for each branch revision, a forest can follow.
729 * However here the forests in decreasing order match the order
730 * of the branch revisions.
732 while (e < rfile.revt) {
733 while (y <= x && !prefix(y->num, e->num))
737 y->branch = forest(y, &e);
745 struct rev *trunk(struct rev *b)
747 struct rev *e = b ? range(b) : NULL, *x, *y = e;
750 for (x = b; x < e - 1; x++)
753 /* For each trunk revision, a forest can follow.
754 * The forests are in increasing order, in contrast to the
755 * trunk revisions, which are in decreasing order.
757 while (e < rfile.revt) {
758 /* search the trunk revision this forest belongs to */
759 while (y > b && !prefix((--y)->num, e->num))
761 assert(y > b || prefix(y->num, e->num));
762 /* parse the forest, set branch of the trunk revision */
763 y->branch = forest(y, &e);
769 int ndeltas, nfiles, nbranches;
772 void rcsfputs(const char *s, FILE *stream)
774 for (putc('@', stream); *s; putc(*s++, stream))
779 void puttree(struct rev *x, FILE *stream)
782 void putforest(struct rev *x, FILE *stream);
783 for (y = x; y; y = y->next) {
785 tm = gmtime(&y->date);
786 fprintf(stream, "%s\n", y->num);
787 fprintf(stream, "date\t%d.%02d.%02d.%02d.%02d.%02d;"
788 "\tauthor %s;\tstate %s;\n",
789 tm->tm_year > 99 ? tm->tm_year + 1900 : tm->tm_year,
790 tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min,
791 tm->tm_sec, y->author, y->state);
792 fprintf(stream, "branches");
793 for (z = y->branch; z; z = z->sub)
794 fprintf(stream, "\n\t%s", z->num);
796 fprintf(stream, ";\n");
797 fprintf(stream, "next\t%s;\n", y->next ? y->next->num : "");
798 fprintf(stream, "\n");
800 putforest(x, stream);
802 void putforest(struct rev *x, FILE *stream)
807 putforest(x->next, stream);
808 for (z = x->branch; z; z = z->sub)
814 if (que == queue + queuel)
816 fcntl(fileno(srv), F_SETFL, O_NONBLOCK);
817 if ((s = write(fileno(srv), que, queue + queuel - que)) != -1)
819 fcntl(fileno(srv), F_SETFL, 0L);
822 void puttree2(struct rev *z, struct rev *x)
825 for (y = x; y; y = y->next) {
826 void putfor2(struct rev *z, struct rev *x);
829 for (z = y; z && !strcmp(z->state, "dead"); z = z->next)
832 sprintf(getq(10*5+5+3+2+3+2+strlen(z->num)
833 + (fi2 - strrchr(fi, '/')) - 1),
840 (int)(fi2 - strrchr(fi, '/') - 1),
841 strrchr(fi, '/') + 1);
842 queue[queuel-1] = '\n';
844 /* file was initially added on branch */
846 } else if (strcmp(y->state, "dead") && z != y) {
847 sprintf(getq(10*6+5+3+2+2+3+2+strlen(z->num)
849 + (fi2 - strrchr(fi, '/'))),
856 "diff", z->num, y->num,
857 (int)(fi2 - strrchr(fi, '/') - 1),
858 strrchr(fi, '/') + 1);
859 queue[queuel-1] = '\n';
862 putfor2(z, y->branch);
865 void putfor2(struct rev *z, struct rev *x)
872 void puttree3(struct rev *z, struct rev *x)
875 for (y = x; y; y = y->next) {
876 void putfor3(struct rev *z, struct rev *x);
877 fprintf(stderr, "%s ", y->num);
880 fprintf(rcsfiop, "\n\n%s\nlog\n", y->num);
881 rcsfputs(y->log, rcsfiop);
882 fprintf(rcsfiop, "\ntext\n@");
885 for (z = y; z && !strcmp(z->state, "dead"); z = z->next)
892 /* file was initially added on branch */
894 } else if (strcmp(y->state, "dead") && z != y) {
900 fprintf(rcsfiop, "@\n");
901 putfor3(z, y->branch);
904 void putfor3(struct rev *z, struct rev *x)
913 /* table of comment leader pairs, merged from RCS and CVS */
914 static const struct clpair {
915 char *suffix, *comlead;
917 { "a" , "-- " }, /* Ada */
921 { "asm" , ";; " }, /* assembler (MS-DOS) */
922 { "bas" , "' " }, /* Visual Basic code */
923 { "bat" , ":: " }, /* batch (MS-DOS) */
924 { "body", "-- " }, /* Ada */
925 { "c" , " * " }, /* C */
926 { "c++" , "// " }, /* C++ in all its infinite guises */
930 { "cl" , ";;; "}, /* Common Lisp */
931 { "cmd" , ":: " }, /* command (OS/2) */
932 { "cmf" , "c " }, /* CM Fortran */
933 { "cs" , " * " }, /* C* */
934 { "csh" , "# " }, /* shell */
935 { "dlg" , " * " }, /* MS Windows dialog file */
936 { "e" , "# " }, /* efl */
937 { "epsf", "% " }, /* encapsulated postscript */
938 { "epsi", "% " }, /* encapsulated postscript */
939 { "el" , "; " }, /* Emacs Lisp */
940 { "f" , "c " }, /* Fortran */
942 { "frm" , "' " }, /* Visual Basic form */
943 { "h" , " * " }, /* C-header */
945 { "hpp" , "// " }, /* C++ header */
947 { "in" , "# " }, /* for Makefile.in */
948 { "l" , " * " }, /* lex (NOTE: franzlisp disagrees) */
949 { "lisp", ";;; "}, /* Lucid Lisp */
950 { "lsp" , ";; " }, /* Microsoft Lisp */
951 { "m" , "// " }, /* Objective C */
952 { "mac" , ";; " }, /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
953 { "mak" , "# " }, /* makefile, e.g. Visual C++ */
954 { "me" , ".\\\" "}, /* troff -me */
955 { "ml" , "; " }, /* mocklisp */
956 { "mm" , ".\\\" "}, /* troff -mm */
957 { "ms" , ".\\\" "}, /* troff -ms */
958 { "man" , ".\\\" "}, /* man-macros t/nroff */
959 { "1" , ".\\\" "}, /* feeble attempt at man pages... */
968 { "p" , " * " }, /* Pascal */
970 { "pl" , "# " }, /* perl (conflict with Prolog) */
971 { "ps" , "% " }, /* PostScript */
972 { "psw" , "% " }, /* postscript wrap */
973 { "pswm", "% " }, /* postscript wrap */
974 { "r" , "# " }, /* ratfor */
975 { "rc" , " * " }, /* Microsoft Windows resource file */
976 { "red" , "% " }, /* psl/rlisp */
978 { "s" , "! " }, /* assembler */
981 { "s" , "| " }, /* assembler */
984 { "s" , "/ " }, /* assembler */
987 { "s" , "# " }, /* assembler */
990 { "s" , "# " }, /* assembler */
991 { "S" , "# " }, /* Macro assembler */
993 { "sh" , "# " }, /* shell */
994 { "sl" , "% " }, /* psl */
995 { "spec", "-- " }, /* Ada */
996 { "sty" , "% " }, /* LaTeX style */
997 { "tex" , "% " }, /* TeX */
998 { "y" , " * " }, /* yacc */
999 { "ye" , " * " }, /* yacc-efl */
1000 { "yr" , " * " }, /* yacc-ratfor */
1001 { "" , "# " }, /* default for empty suffix */
1002 { 0 , "# " } /* default for unknown suffix; must be last */
1005 static int is_version_number(const char *line)
1007 if (*line < '0' || *line > '9')
1009 for (; *line; line++)
1010 if (*line != '\n' && *line != '.' &&
1011 (*line < '0' || *line > '9'))
1016 static int is_done(const char *filename)
1018 char buffer[1024], line[1024];
1020 int remaining = rfile.sel;
1022 snprintf(buffer, sizeof(buffer), "%s.old", filename);
1023 orig = fopen(buffer, "rb");
1027 if (!fgets(line, sizeof(line), orig) || strncmp(line, "head\t", 5)) {
1032 while (!feof(orig) && fgets(line, sizeof(line), orig)) {
1033 if (!strcmp(line, "desc\n"))
1035 if (is_version_number(line))
1041 rename(buffer, filename);
1047 static const char *current_file;
1048 static void unlink_current_file(int dummy)
1051 unlink(current_file);
1057 size_t l = strlen(cvsdir);
1058 char **i, suffix[6] = "";
1060 fprintf(stderr, "%s\n", rfile.source);
1061 fi = strrchr(rfile.source, '/');
1062 sprintf(getq(12 + (fi - rfile.source) + 1),
1063 "Directory .\n%.*s",
1064 (int)(fi - rfile.source), rfile.source);
1065 queue[queuel-1] = '\n';
1066 fi2 = strrchr(fi, ',');
1067 fi = strrchr(fi, '.');
1068 assert(fi2 && (!fi || fi < fi2));
1071 /* If the suffix length is greater than four characters,
1072 * it cannot match, since it copies five of them.
1074 strncpy(suffix, fi, fi2 - fi < sizeof suffix - 1
1075 ? fi2 - fi : sizeof suffix - 1);
1077 fi = rfile.source + l + 1;
1081 rcsfiop = fopen(fi, "w");
1083 fprintf(rcsfiop, "head\t%s;\n", rfile.head.num ? rfile.head.num : "");
1084 if (rfile.branch.num)
1085 fprintf(rcsfiop, "branch\t%s;\n", rfile.branch.num);
1086 fprintf(rcsfiop, "access");
1087 for (i = rfile.accl; i < rfile.acct; i++)
1088 fprintf(rcsfiop, "\n\t%s", *i);
1089 fprintf(rcsfiop, ";\n");
1090 fprintf(rcsfiop, "symbols");
1091 for (j = rfile.tagl; j < rfile.tagt; j++)
1092 fprintf(rcsfiop, "\n\t%s:%s", j->item, j->rev.num);
1093 fprintf(rcsfiop, ";\n");
1094 fprintf(rcsfiop, "locks");
1095 for (j = rfile.lckl; j < rfile.lckt; j++)
1096 fprintf(rcsfiop, "\n\t%s:%s", j->item, j->rev.num);
1097 fprintf(rcsfiop, ";%s\n", rfile.strict ? " strict;" : "");
1098 fprintf(rcsfiop, "comment\t");
1100 rcsfputs(rfile.leader, rcsfiop);
1102 const struct clpair *curr;
1103 for (curr = cltbl; curr->suffix; curr++)
1104 if (!strcmp(curr->suffix, suffix))
1106 rcsfputs(curr->comlead, rcsfiop);
1108 fprintf(rcsfiop, ";\n");
1109 if (rfile.ksub && strcmp(rfile.ksub, "kv")) {
1110 fprintf(rcsfiop, "expand\t");
1111 rcsfputs(rfile.ksub, rcsfiop);
1112 fprintf(rcsfiop, ";\n");
1114 fprintf(rcsfiop, "\n\n");
1117 puttree(rfile.revl, rcsfiop);
1118 fprintf(rcsfiop, "\ndesc\n");
1119 rcsfputs(rfile.descr, rcsfiop);
1120 fprintf(rcsfiop, "\n");
1121 puttree2(NULL, rfile.revl);
1123 puttree3(NULL, rfile.revl);
1127 fprintf(stderr, "\n");
1129 current_file = NULL;
1132 int main(int argc, char *argv[])
1135 char *buf, *passfile, *fn0, *fn1, *fn2;
1137 if (argc == 5 || argc == 3)
1138 if (!(rlog_input = fopen(argv[--argc], "rb"))) {
1139 fprintf(stderr, "Invalid rlog file\n");
1140 return EXIT_FAILURE;
1143 if (argc == 4 && !strcmp(argv[1], "-d")) {
1147 } else if (argc != 2) {
1148 fprintf(stderr, "Argument count.\n");
1149 return EXIT_FAILURE;
1151 cvsroot = getenv("CVSROOT");
1154 fprintf(stderr, "No CVSROOT.\n");
1155 return EXIT_FAILURE;
1157 cvsroot = strdup(cvsroot);
1158 passfile = (home = getenv("HOME"))
1159 ? strcat(strcpy(malloc(strlen(home)
1160 + sizeof "/.cvspass"), home), "/.cvspass")
1161 : strcpy(malloc(sizeof ".cvspass"), ".cvspass");
1163 if (!(yyin = fopen(passfile, "r")) || yylex() == EOF) {
1164 size_t l = strlen(cvsroot);
1165 cvsroot = realloc(cvsroot, l + 2);
1166 cvspass = strcpy(cvsroot + l + 1, "A");
1170 fprintf(stderr, "password file: %s\n"
1172 "pass: %.1s\n", passfile,
1175 signal(SIGINT, unlink_current_file);
1176 if (!(srv = srvopen())) {
1177 fprintf(stderr, "server connection failed or bad cvsroot\n");
1179 return EXIT_FAILURE;
1183 fprintf(srv, "BEGIN AUTH REQUEST\n"
1187 "END AUTH REQUEST\n", cvsdir, cvsuser, cvspass);
1188 fprintf(srv, "Root %s\n", cvsdir);
1189 /*fprintf(srv, "Argument micq/m4\n");*/
1190 fprintf(srv, "Argument %s\n", argv[1]);
1192 yyrestart(rlog_input);
1196 fprintf(srv, "rlog\n");
1200 yy_set_interactive(1);
1201 fprintf(stderr, "exit: %d\n", yylex());
1203 fn1 = queue + queuel;
1206 fn2 = fn0 = malloc(strlen(argv[1]) + 3);
1207 fn1 = strlen(argv[1]) + 3 + strcpy(fn0, argv[1]);
1210 while (fn0 < fn1 && (buf = ftobuf(rcsfiop = fopen(fn0, "r"), &siz))) {
1212 fn0 += strlen(fn0) + 1;
1213 buf[siz - 2] = YY_END_OF_BUFFER_CHAR;
1214 buf[siz - 1] = YY_END_OF_BUFFER_CHAR;
1215 /* fprintf(stderr, "<<%s>>\n", buf); */
1216 yybuf = YY_CURRENT_BUFFER;
1217 yy_scan_buffer(buf, siz);
1218 yy_set_interactive(0);
1222 assert(YY_CURRENT_BUFFER);
1223 yy_delete_buffer(YY_CURRENT_BUFFER);
1224 yy_switch_to_buffer(yybuf);
1239 fprintf(stderr, "%s clone successful: %d files, "
1240 "%d branches, %d deltas\n",
1241 argv[0], stats.nfiles, stats.nbranches, stats.ndeltas);
1245 return EXIT_SUCCESS;