/[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 12 - (hide annotations)
Sat Mar 13 17:22:53 2004 UTC (20 years, 1 month ago) by dpavlin
File MIME type: text/plain
File size: 8149 byte(s)
added documentation

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 1 # ok, now do the checkout
72    
73 dpavlin 8 log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");
74 dpavlin 1
75 dpavlin 7 chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";
76    
77    
78 dpavlin 3 my $rev;
79    
80 dpavlin 1 # check if svnrev exists
81 dpavlin 7 if (! -e ".svnrev") {
82 dpavlin 1 print <<_USAGE_;
83    
84 dpavlin 3 Your CVS repository doesn't have .svnrev file!
85 dpavlin 1
86 dpavlin 12 This file is used to keep CVS repository and Subversion in sync, so
87 dpavlin 3 that only newer changes will be commited.
88 dpavlin 1
89 dpavlin 3 It's quote possible that this is first svn2cvs run for this repository.
90     If so, you will have to identify correct svn revision which
91     corresponds to current version of CVS repository that has just
92     been checkouted.
93 dpavlin 1
94 dpavlin 3 If you migrated your cvs repository to svn using cvs2svn, this will be
95 dpavlin 12 last Subversion revision. If this is initial run of conversion of
96     Subversion repository to CVS, correct revision is 0.
97 dpavlin 1
98     _USAGE_
99 dpavlin 3
100     print "svn revision corresponding to CVS [abort]: ";
101     my $in = <STDIN>;
102     chomp($in);
103     if ($in !~ /^\d+$/) {
104     print "Aborting: revision not a number\n";
105     exit 1;
106     } else {
107     $rev = $in;
108     commit_svnrev($rev,1); # create new
109     }
110     } else {
111 dpavlin 7 open(SVNREV,".svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
112 dpavlin 6 $rev = <SVNREV>;
113 dpavlin 3 chomp($rev);
114     close(SVNREV);
115 dpavlin 1 }
116    
117     print "Starting after revision $rev\n";
118 dpavlin 2 $rev++;
119 dpavlin 1
120    
121     #
122     # FIXME!! HEAD should really be next verison and loop because this way we
123     # loose multiple edits of same file and corresponding messages. On the
124     # other hand, if you want to compress your traffic to CVS server and don't
125     # case much about accuracy and completnes of logs there, this might
126     # be good. YMMV
127     #
128 dpavlin 8 open(LOG, "svn log -r $rev:HEAD -v --xml $SVNROOT |") || die "svn log for repository $SVNROOT failed: $!";
129 dpavlin 1 my $log;
130     while(<LOG>) {
131     $log .= $_;
132     }
133     close(LOG);
134    
135 dpavlin 2 my $xml = XMLin($log, ForceArray => [ 'logentry', 'path' ]);
136 dpavlin 1
137    
138 dpavlin 12 #=begin log_example
139     #
140     #------------------------------------------------------------------------
141     #r256 | dpavlin | 2004-03-09 13:18:17 +0100 (Tue, 09 Mar 2004) | 2 lines
142     #
143     #ported r254 from hidra branch
144     #
145     #=cut
146 dpavlin 1
147     my $fmt = "\n" . "-" x 79 . "\nr%5s| %8s | %s\n\n%s\n";
148    
149 dpavlin 2 if (! $xml->{'logentry'}) {
150 dpavlin 12 print "no newer log entries in Subversion repostory. CVS is current\n";
151 dpavlin 2 exit 0;
152     }
153    
154 dpavlin 1 foreach my $e (@{$xml->{'logentry'}}) {
155     die "BUG: revision from .svnrev ($rev) greater than from subversion (".$e->{'revision'}.")" if ($rev > $e->{'revision'});
156     $rev = $e->{'revision'};
157 dpavlin 8 log_system("svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP", "svn export of revision $rev failed");
158 dpavlin 1
159 dpavlin 8 # deduce name of svn directory
160     my $SVNREP = "";
161     my $tmpsvn = $SVNROOT || die "BUG: SVNROOT empty!";
162     my $tmppath = $e->{'paths'}->{'path'}->[0]->{'content'} || die "BUG: tmppath empty!";
163     do {
164     print "## tmppath: $tmppath tmpsvn: $tmpsvn SVNREP: $SVNREP\n";
165     if ($tmpsvn =~ s,(/\w+/*)$,,) {
166     $SVNREP .= $1;
167     } else {
168     die "ERROR: can't deduce svn dir from $SVNROOT.\nUsing root of snv repository for current version instead of /trunk/ is not supported.\n";
169     }
170     } until ($tmppath =~ m/^$SVNREP/);
171    
172     print "NOTICE: using $SVNREP as directory for svn\n";
173    
174 dpavlin 1 printf($fmt, $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'});
175     foreach my $p (@{$e->{'paths'}->{'path'}}) {
176     my ($action,$path) = ($p->{'action'},$p->{'content'});
177    
178     print "svn2cvs: $action $path\n";
179    
180     # prepare path and message
181     my $file = $path;
182 dpavlin 8 $path =~ s,^$SVNREP/*,, || die "BUG: can't strip SVNREP from path";
183 dpavlin 7
184     if (! $path) {
185     print "NOTICE: skipped this operation. Probably trunk creation\n";
186     next;
187     }
188    
189 dpavlin 1 my $msg = $e->{'msg'};
190 dpavlin 11 $msg =~ s/'/\\'/g; # quote "
191 dpavlin 1
192     if ($action =~ /M/) {
193     print "svn2cvs: modify $path -- nop\n";
194     } elsif ($action =~ /A/) {
195 dpavlin 8 if (-d $path) {
196     chdir($path) || die "can't cd into dir $path for import: $!";
197 dpavlin 11 log_system("$cvs import -d -m '$msg' $CVSREP/$path svn r$rev", "cvs import of $path failed");
198 dpavlin 8 chdir("$TMPDIR") || die "can't cd to $TMPDIR/$CVSREP: $!";
199     log_system("$cvs checkout $CVSREP/$path", "cvs checkout of imported dir $path failed");
200     chdir("$TMPDIR/$CVSREP") || die "can't cd back to $TMPDIR/$CVSREP: $!";
201     } else {
202 dpavlin 11 log_system("$cvs add -m '$msg' $path", "cvs add of $path failed");
203 dpavlin 8 }
204 dpavlin 1 } elsif ($action =~ /D/) {
205 dpavlin 11 log_system("$cvs delete -m '$msg' $path", "cvs delete of $path failed");
206 dpavlin 1 } else {
207     print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
208     }
209    
210     # now commit changes
211 dpavlin 11 log_system("$cvs commit -m '$msg' $path", "cvs commit of $path failed");
212 dpavlin 1
213     }
214    
215     commit_svnrev($rev);
216     }
217 dpavlin 12
218     __END__
219    
220     =pod
221    
222     =head1 NAME
223    
224     svn2cvs - save subversion commits to (read-only) cvs repository
225    
226     =head1 SYNOPSIS
227    
228     ./svn2cvs.pl SVN_URL CVSROOT CVSREPOSITORY
229    
230     Usage example (used to self-host this script):
231    
232     ./svn2cvs.pl file:///home/dpavlin/private/svn/svn2cvs/trunk/ \
233     :pserver:dpavlin@cvs.tigris.org:/cvs svn2cvs/src
234    
235     =head1 DESCRIPTION
236    
237     This script will allow you to commit changes made to Subversion repository also to (read-only) CVS repository manually or from Subversion's C<post-commit> hook.
238    
239     It's using F<.svnrev> file (which will be created on first run) in
240     B<CVSROOT/CVSREPOSITORY> to store last Subversion revision which was
241     committed into CVS.
242    
243     One run will do following things:
244    
245     =over 4
246    
247     =item *
248     checkout B<CVSREPOSITORY> from B<CVSROOT> to temporary directory
249    
250     =item *
251     check if F<.svnrev> file exists and create it if it doesn't
252    
253     =item *
254     loop through all revisions from current in B<CVSROOT/CVSREPOSITORY> (using
255     F<.svnrev>) up to B<HEAD> (current one)
256    
257     =over 5
258    
259     =item *
260     checkout next Subversion revision from B<SVN_URL> over CVS checkout
261     temporary directory
262    
263     =item *
264     make modification (add and/or delete) done in that revision
265    
266     =item *
267     commit modification (added, deleted or modified files/dirs) while
268     preserving original message from CVS
269    
270     =item *
271     update F<.svnrev> to match current revision
272    
273     =back
274    
275     =item *
276     cleanup temporary directory
277    
278     =back
279    
280     If checkout fails for some reason (e.g. flaky ssh connection), you will
281     still have valid CVS repository, so all you have to do is run B<svn2cvs.pl>
282     again.
283    
284     =head1 WARNINGS
285    
286     "Cheap" copy operations in Subversion are not at all cheap in CVS. They will
287     create multiple copies of files in CVS repository!
288    
289     =head1 RELATED PROJECTS
290    
291     B<Subversion> L<http://subversion.tigris.org/> version control system that is a
292     compelling replacement for CVS in the open source community.
293    
294     B<cvs2svn> L<http://cvs2svn.tigris.org/> converts a CVS repository to a
295     Subversion repository. It is designed for one-time conversions, not for
296     repeated synchronizations between CVS and Subversion.
297    
298     =head1 AUTHOR
299    
300     Dobrica Pavlinusic <dpavlin@rot13.org>
301    
302     L<https://www.rot13.org/~dpavlin/>
303    
304     =head1 LICENSE
305    
306     This product is licensed under GNU Public License (GPL) v2 or later.
307    
308     =cut
309    

Properties

Name Value
svn:executable

  ViewVC Help
Powered by ViewVC 1.1.26