/[svn2cvs]/trunk/svn2cvs.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /trunk/svn2cvs.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 20 - (hide annotations)
Mon Apr 18 16:43:08 2005 UTC (19 years ago) by dpavlin
File MIME type: text/plain
File size: 11167 byte(s)
fixed path deduction bug reported by Philippe Cote (thanks)

1 dpavlin 1 #!/usr/bin/perl -w
2    
3     # This script will transfer changes from Subversion repository
4     # to CVS repository (e.g. SourceForge) while preserving commit
5     # logs.
6     #
7     # Based on original shell version by Tollef Fog Heen available at
8     # http://raw.no/personal/blog
9     #
10     # 2004-03-09 Dobrica Pavlinusic <dpavlin@rot13.org>
11 dpavlin 12 #
12     # documentation is after __END__
13 dpavlin 1
14     use strict;
15     use File::Temp qw/ tempdir /;
16     use Data::Dumper;
17     use XML::Simple;
18    
19 dpavlin 8 if (@ARGV < 2) {
20     print "usage: $0 SVN_URL CVSROOT CVSREPOSITORY\n";
21     exit 1;
22     }
23 dpavlin 1
24 dpavlin 8 my ($SVNROOT,$CVSROOT, $CVSREP) = @ARGV;
25 dpavlin 1
26 dpavlin 10 if ($SVNROOT !~ m,^[\w+]+:///*\w+,) {
27 dpavlin 8 print "ERROR: invalid svn root $SVNROOT\n";
28     exit 1;
29     }
30 dpavlin 7
31 dpavlin 1 my $TMPDIR=tempdir( "/tmp/checkoutXXXXX", CLEANUP => 1 );
32    
33     chdir($TMPDIR) || die "can't cd to $TMPDIR: $!";
34    
35     # cvs command with root
36     my $cvs="cvs -d $CVSROOT";
37    
38     #
39     # sub to do logging and system calls
40     #
41     sub log_system($$) {
42     my ($cmd,$errmsg) = @_;
43     print STDERR "## $cmd\n";
44 dpavlin 8 system($cmd) == 0 || die "$errmsg: $!";
45 dpavlin 1 }
46    
47 dpavlin 3 #
48     # sub to commit .svn rev file later
49     #
50     sub commit_svnrev {
51     my $rev = shift @_;
52     my $add_new = shift @_;
53 dpavlin 1
54 dpavlin 3 die "commit_svnrev needs revision" if (! defined($rev));
55    
56 dpavlin 7 open(SVNREV,"> .svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
57 dpavlin 3 print SVNREV $rev;
58     close(SVNREV);
59    
60     my $path=".svnrev";
61    
62     if ($add_new) {
63 dpavlin 7 system "$cvs add $path" || die "cvs add of $path failed: $!";
64 dpavlin 4 } else {
65     my $msg="subversion revision $rev commited to CVS";
66     print "$msg\n";
67 dpavlin 11 system "$cvs commit -m '$msg' $path" || die "cvs commit of $path failed: $!";
68 dpavlin 3 }
69     }
70    
71 dpavlin 18 # current revision in CVS
72     my $rev;
73    
74 dpavlin 1 # ok, now do the checkout
75 dpavlin 18 eval {
76     log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");
77     };
78 dpavlin 1
79 dpavlin 18 if ($@) {
80     print <<_NEW_REP_;
81 dpavlin 1
82 dpavlin 18 There is no CVS repository '$CVSREP' in your CVS. I will assume that
83     this is import of new module in your CVS and start from revision 0.
84 dpavlin 7
85 dpavlin 18 Press enter to continue importing new CVS repository or CTRL+C to abort.
86 dpavlin 7
87 dpavlin 18 _NEW_REP_
88 dpavlin 3
89 dpavlin 18 print "start import of new module [yes]: ";
90     my $in = <STDIN>;
91     mkdir($CVSREP) || die "can't create $CVSREP: $!";
92 dpavlin 1
93 dpavlin 18 chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";
94    
95     open(SVNREV,"> .svnrev") || die "can't open $CVSREP/.svnrev: $!";
96     print SVNREV "0";
97     close(SVNREV);
98    
99     $rev = 0;
100    
101     # create new module
102     log_system("$cvs import -m 'new CVS module' $CVSREP svn2cvs r0", "can't import new module into $CVSREP");
103    
104     unlink ".svnrev" || die "can't remove .svnrev: $!";
105     chdir($TMPDIR) || die "can't cd to $TMPDIR: $!";
106     rmdir $CVSREP || die "can't remove $CVSREP: $!";
107    
108     # and checkout it
109     log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");
110    
111     chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";
112    
113     } else {
114    
115     # import into existing module directory in CVS
116    
117     chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";
118    
119    
120     # check if svnrev exists
121     if (! -e ".svnrev") {
122     print <<_USAGE_;
123    
124 dpavlin 3 Your CVS repository doesn't have .svnrev file!
125 dpavlin 1
126 dpavlin 12 This file is used to keep CVS repository and Subversion in sync, so
127 dpavlin 3 that only newer changes will be commited.
128 dpavlin 1
129 dpavlin 3 It's quote possible that this is first svn2cvs run for this repository.
130     If so, you will have to identify correct svn revision which
131     corresponds to current version of CVS repository that has just
132     been checkouted.
133 dpavlin 1
134 dpavlin 3 If you migrated your cvs repository to svn using cvs2svn, this will be
135 dpavlin 12 last Subversion revision. If this is initial run of conversion of
136     Subversion repository to CVS, correct revision is 0.
137 dpavlin 1
138     _USAGE_
139 dpavlin 3
140 dpavlin 18 print "svn revision corresponding to CVS [abort]: ";
141     my $in = <STDIN>;
142     chomp($in);
143     if ($in !~ /^\d+$/) {
144     print "Aborting: revision not a number\n";
145     exit 1;
146     } else {
147     $rev = $in;
148     commit_svnrev($rev,1); # create new
149     }
150 dpavlin 3 } else {
151 dpavlin 18 open(SVNREV,".svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
152     $rev = <SVNREV>;
153     chomp($rev);
154     close(SVNREV);
155 dpavlin 3 }
156 dpavlin 18
157     print "Starting after revision $rev\n";
158     $rev++;
159 dpavlin 1 }
160    
161    
162     #
163     # FIXME!! HEAD should really be next verison and loop because this way we
164     # loose multiple edits of same file and corresponding messages. On the
165     # other hand, if you want to compress your traffic to CVS server and don't
166     # case much about accuracy and completnes of logs there, this might
167     # be good. YMMV
168     #
169 dpavlin 8 open(LOG, "svn log -r $rev:HEAD -v --xml $SVNROOT |") || die "svn log for repository $SVNROOT failed: $!";
170 dpavlin 1 my $log;
171     while(<LOG>) {
172     $log .= $_;
173     }
174     close(LOG);
175    
176 dpavlin 2 my $xml = XMLin($log, ForceArray => [ 'logentry', 'path' ]);
177 dpavlin 1
178    
179 dpavlin 12 #=begin log_example
180     #
181     #------------------------------------------------------------------------
182     #r256 | dpavlin | 2004-03-09 13:18:17 +0100 (Tue, 09 Mar 2004) | 2 lines
183     #
184     #ported r254 from hidra branch
185     #
186     #=cut
187 dpavlin 1
188     my $fmt = "\n" . "-" x 79 . "\nr%5s| %8s | %s\n\n%s\n";
189    
190 dpavlin 2 if (! $xml->{'logentry'}) {
191 dpavlin 12 print "no newer log entries in Subversion repostory. CVS is current\n";
192 dpavlin 2 exit 0;
193     }
194    
195 dpavlin 14 # check if file exists in CVS/Entries
196     sub in_entries($) {
197     my $path = shift;
198     if ($path !~ m,^(.*?/*)([^/]+)$,) {
199     die "can't split '$path' to dir and file!";
200     } else {
201     my ($d,$f) = ($1,$2);
202 dpavlin 17 if ($d !~ m,/$, && $d ne "") {
203 dpavlin 14 $d .= "/";
204     }
205     open(E, $d."CVS/Entries") || die "can't open ${d}CVS/Entries: $!";
206     while(<E>) {
207     return(1) if (m,^/$f/,);
208     }
209     close(E);
210     return 0;
211     }
212     }
213    
214 dpavlin 1 foreach my $e (@{$xml->{'logentry'}}) {
215     die "BUG: revision from .svnrev ($rev) greater than from subversion (".$e->{'revision'}.")" if ($rev > $e->{'revision'});
216     $rev = $e->{'revision'};
217 dpavlin 8 log_system("svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP", "svn export of revision $rev failed");
218 dpavlin 1
219 dpavlin 8 # deduce name of svn directory
220     my $SVNREP = "";
221     my $tmpsvn = $SVNROOT || die "BUG: SVNROOT empty!";
222     my $tmppath = $e->{'paths'}->{'path'}->[0]->{'content'} || die "BUG: tmppath empty!";
223     do {
224 dpavlin 20 if ($tmpsvn =~ s#(/[^/]+)/*$##) {
225     $SVNREP = $1 . $SVNREP;
226 dpavlin 8 } else {
227 dpavlin 15 print "NOTICE: can't deduce svn dir from $SVNROOT - skipping\n";
228     next;
229 dpavlin 8 }
230     } until ($tmppath =~ m/^$SVNREP/);
231    
232     print "NOTICE: using $SVNREP as directory for svn\n";
233    
234 dpavlin 1 printf($fmt, $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'});
235 dpavlin 18 my @commit;
236    
237 dpavlin 1 foreach my $p (@{$e->{'paths'}->{'path'}}) {
238     my ($action,$path) = ($p->{'action'},$p->{'content'});
239    
240     print "svn2cvs: $action $path\n";
241    
242     # prepare path and message
243     my $file = $path;
244 dpavlin 18 $path =~ s,^$SVNREP/*,, || die "BUG: can't strip SVNREP '$SVNREP' from path";
245 dpavlin 7
246     if (! $path) {
247     print "NOTICE: skipped this operation. Probably trunk creation\n";
248     next;
249     }
250    
251 dpavlin 1 my $msg = $e->{'msg'};
252 dpavlin 14 $msg =~ s/'/'\\''/g; # quote "
253 dpavlin 1
254     if ($action =~ /M/) {
255     print "svn2cvs: modify $path -- nop\n";
256     } elsif ($action =~ /A/) {
257 dpavlin 8 if (-d $path) {
258     chdir($path) || die "can't cd into dir $path for import: $!";
259 dpavlin 11 log_system("$cvs import -d -m '$msg' $CVSREP/$path svn r$rev", "cvs import of $path failed");
260 dpavlin 18 if (-d "$CVSREP/$path") {
261     rmdir "$CVSREP/$path" || die "can't remove $CVSREP/$path: $!";
262     } else {
263     unlink "$CVSREP/$path" || die "can't remove $CVSREP/$path: $!";
264     }
265 dpavlin 8 chdir("$TMPDIR") || die "can't cd to $TMPDIR/$CVSREP: $!";
266     log_system("$cvs checkout $CVSREP/$path", "cvs checkout of imported dir $path failed");
267     chdir("$TMPDIR/$CVSREP") || die "can't cd back to $TMPDIR/$CVSREP: $!";
268 dpavlin 14 } elsif ($path =~ m,^(.+)/[^/]+$, && ! -e "$1/CVS/Root") {
269     my $dir = $1;
270     in_entries($dir) || log_system("$cvs add $dir", "cvs add of dir $dir failed");
271     in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");
272 dpavlin 8 } else {
273 dpavlin 14 in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");
274 dpavlin 8 }
275 dpavlin 1 } elsif ($action =~ /D/) {
276 dpavlin 14 unlink $path || die "can't delete $path: $!";
277     log_system("$cvs delete $path", "cvs delete of $path failed");
278 dpavlin 1 } else {
279     print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
280     }
281    
282 dpavlin 18 # save commits for later
283     push @commit, $path;
284 dpavlin 1
285     }
286    
287 dpavlin 18 my $msg = $e->{'msg'};
288     $msg =~ s/'/'\\''/g; # quote "
289    
290     # now commit changes
291     log_system("$cvs commit -m '$msg' ".join(" ",@commit), "cvs commit of ".join(",",@commit)." failed");
292    
293 dpavlin 1 commit_svnrev($rev);
294     }
295 dpavlin 12
296     __END__
297    
298     =pod
299    
300     =head1 NAME
301    
302     svn2cvs - save subversion commits to (read-only) cvs repository
303    
304     =head1 SYNOPSIS
305    
306     ./svn2cvs.pl SVN_URL CVSROOT CVSREPOSITORY
307    
308     Usage example (used to self-host this script):
309    
310     ./svn2cvs.pl file:///home/dpavlin/private/svn/svn2cvs/trunk/ \
311     :pserver:dpavlin@cvs.tigris.org:/cvs svn2cvs/src
312    
313     =head1 DESCRIPTION
314    
315 dpavlin 18 This script will allows you to commit changes made to Subversion repository to
316     (read-only) CVS repository manually or from Subversion's C<post-commit> hook.
317 dpavlin 12
318     It's using F<.svnrev> file (which will be created on first run) in
319     B<CVSROOT/CVSREPOSITORY> to store last Subversion revision which was
320     committed into CVS.
321    
322     One run will do following things:
323    
324     =over 4
325    
326     =item *
327     checkout B<CVSREPOSITORY> from B<CVSROOT> to temporary directory
328    
329     =item *
330     check if F<.svnrev> file exists and create it if it doesn't
331    
332     =item *
333     loop through all revisions from current in B<CVSROOT/CVSREPOSITORY> (using
334     F<.svnrev>) up to B<HEAD> (current one)
335    
336     =over 5
337    
338     =item *
339     checkout next Subversion revision from B<SVN_URL> over CVS checkout
340     temporary directory
341    
342     =item *
343     make modification (add and/or delete) done in that revision
344    
345     =item *
346     commit modification (added, deleted or modified files/dirs) while
347     preserving original message from CVS
348    
349     =item *
350     update F<.svnrev> to match current revision
351    
352     =back
353    
354     =item *
355     cleanup temporary directory
356    
357     =back
358    
359     If checkout fails for some reason (e.g. flaky ssh connection), you will
360     still have valid CVS repository, so all you have to do is run B<svn2cvs.pl>
361     again.
362    
363     =head1 WARNINGS
364    
365     "Cheap" copy operations in Subversion are not at all cheap in CVS. They will
366     create multiple copies of files in CVS repository!
367    
368 dpavlin 18 This script assume that you want to sync your C<trunk> (or any other
369     directory for that matter) directory with CVS, not root of your subversion.
370     This might be considered bug, but since common practise is to have
371     directories C<trunk> and C<branches> in svn and source code in them, it's
372     not serious limitation.
373    
374 dpavlin 12 =head1 RELATED PROJECTS
375    
376     B<Subversion> L<http://subversion.tigris.org/> version control system that is a
377     compelling replacement for CVS in the open source community.
378    
379     B<cvs2svn> L<http://cvs2svn.tigris.org/> converts a CVS repository to a
380     Subversion repository. It is designed for one-time conversions, not for
381     repeated synchronizations between CVS and Subversion.
382    
383 dpavlin 16 =head1 CHANGES
384    
385     Versions of this utility are actually Subversion repository revisions,
386     so they might not be in sequence.
387    
388     =over 3
389    
390     =item r10
391    
392     First release available to public
393    
394     =item r15
395    
396     Addition of comprehensive documentation, fixes for quoting in commit
397     messages, and support for skipping changes which are not under current
398     Subversion checkout root (e.g. branches).
399    
400 dpavlin 19 =item r18
401 dpavlin 18
402     Support for importing your svn into empty CVS repository (it will first
403     create module and than dump all revisions).
404     Group commit operations to save round-trips to CVS server.
405     Documentation improvements and other small fixes.
406    
407 dpavlin 20 =item r20
408 dpavlin 18
409 dpavlin 20 Fixed path deduction (overlap between Subversion reporistory and CVS checkout).
410    
411 dpavlin 16 =back
412    
413 dpavlin 12 =head1 AUTHOR
414    
415     Dobrica Pavlinusic <dpavlin@rot13.org>
416    
417     L<http://www.rot13.org/~dpavlin/>
418    
419     =head1 LICENSE
420    
421     This product is licensed under GNU Public License (GPL) v2 or later.
422    
423     =cut
424    

Properties

Name Value
svn:executable

  ViewVC Help
Powered by ViewVC 1.1.26