/[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

Contents of /trunk/svn2cvs.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 14 - (show annotations)
Sun Mar 14 21:49:36 2004 UTC (20 years, 1 month ago) by dpavlin
File MIME type: text/plain
File size: 8825 byte(s)
check if file is in CVS/Entries before add (to enable broken commits
to continue), correctly quote ' in commit messages for shell

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 #
12 # documentation is after __END__
13
14 use strict;
15 use File::Temp qw/ tempdir /;
16 use Data::Dumper;
17 use XML::Simple;
18
19 if (@ARGV < 2) {
20 print "usage: $0 SVN_URL CVSROOT CVSREPOSITORY\n";
21 exit 1;
22 }
23
24 my ($SVNROOT,$CVSROOT, $CVSREP) = @ARGV;
25
26 if ($SVNROOT !~ m,^[\w+]+:///*\w+,) {
27 print "ERROR: invalid svn root $SVNROOT\n";
28 exit 1;
29 }
30
31 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 system($cmd) == 0 || die "$errmsg: $!";
45 }
46
47 #
48 # sub to commit .svn rev file later
49 #
50 sub commit_svnrev {
51 my $rev = shift @_;
52 my $add_new = shift @_;
53
54 die "commit_svnrev needs revision" if (! defined($rev));
55
56 open(SVNREV,"> .svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
57 print SVNREV $rev;
58 close(SVNREV);
59
60 my $path=".svnrev";
61
62 if ($add_new) {
63 system "$cvs add $path" || die "cvs add of $path failed: $!";
64 } else {
65 my $msg="subversion revision $rev commited to CVS";
66 print "$msg\n";
67 system "$cvs commit -m '$msg' $path" || die "cvs commit of $path failed: $!";
68 }
69 }
70
71 # ok, now do the checkout
72
73 log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");
74
75 chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";
76
77
78 my $rev;
79
80 # check if svnrev exists
81 if (! -e ".svnrev") {
82 print <<_USAGE_;
83
84 Your CVS repository doesn't have .svnrev file!
85
86 This file is used to keep CVS repository and Subversion in sync, so
87 that only newer changes will be commited.
88
89 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
94 If you migrated your cvs repository to svn using cvs2svn, this will be
95 last Subversion revision. If this is initial run of conversion of
96 Subversion repository to CVS, correct revision is 0.
97
98 _USAGE_
99
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 open(SVNREV,".svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
112 $rev = <SVNREV>;
113 chomp($rev);
114 close(SVNREV);
115 }
116
117 print "Starting after revision $rev\n";
118 $rev++;
119
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 open(LOG, "svn log -r $rev:HEAD -v --xml $SVNROOT |") || die "svn log for repository $SVNROOT failed: $!";
129 my $log;
130 while(<LOG>) {
131 $log .= $_;
132 }
133 close(LOG);
134
135 my $xml = XMLin($log, ForceArray => [ 'logentry', 'path' ]);
136
137
138 #=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
147 my $fmt = "\n" . "-" x 79 . "\nr%5s| %8s | %s\n\n%s\n";
148
149 if (! $xml->{'logentry'}) {
150 print "no newer log entries in Subversion repostory. CVS is current\n";
151 exit 0;
152 }
153
154 # check if file exists in CVS/Entries
155 sub in_entries($) {
156 my $path = shift;
157 if ($path !~ m,^(.*?/*)([^/]+)$,) {
158 die "can't split '$path' to dir and file!";
159 } else {
160 my ($d,$f) = ($1,$2);
161 if ($d !~ m,/$,) {
162 $d .= "/";
163 }
164 open(E, $d."CVS/Entries") || die "can't open ${d}CVS/Entries: $!";
165 while(<E>) {
166 return(1) if (m,^/$f/,);
167 }
168 close(E);
169 return 0;
170 }
171 }
172
173 foreach my $e (@{$xml->{'logentry'}}) {
174 die "BUG: revision from .svnrev ($rev) greater than from subversion (".$e->{'revision'}.")" if ($rev > $e->{'revision'});
175 $rev = $e->{'revision'};
176 log_system("svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP", "svn export of revision $rev failed");
177
178 # deduce name of svn directory
179 my $SVNREP = "";
180 my $tmpsvn = $SVNROOT || die "BUG: SVNROOT empty!";
181 my $tmppath = $e->{'paths'}->{'path'}->[0]->{'content'} || die "BUG: tmppath empty!";
182 do {
183 print "## tmppath: $tmppath tmpsvn: $tmpsvn SVNREP: $SVNREP\n";
184 if ($tmpsvn =~ s,(/\w+/*)$,,) {
185 $SVNREP .= $1;
186 } else {
187 die "ERROR: can't deduce svn dir from $SVNROOT.\nUsing root of snv repository for current version instead of /trunk/ is not supported.\n";
188 }
189 } until ($tmppath =~ m/^$SVNREP/);
190
191 print "NOTICE: using $SVNREP as directory for svn\n";
192
193 printf($fmt, $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'});
194 foreach my $p (@{$e->{'paths'}->{'path'}}) {
195 my ($action,$path) = ($p->{'action'},$p->{'content'});
196
197 print "svn2cvs: $action $path\n";
198
199 # prepare path and message
200 my $file = $path;
201 $path =~ s,^$SVNREP/*,, || die "BUG: can't strip SVNREP from path";
202
203 if (! $path) {
204 print "NOTICE: skipped this operation. Probably trunk creation\n";
205 next;
206 }
207
208 my $msg = $e->{'msg'};
209 $msg =~ s/'/'\\''/g; # quote "
210
211 if ($action =~ /M/) {
212 print "svn2cvs: modify $path -- nop\n";
213 } elsif ($action =~ /A/) {
214 if (-d $path) {
215 chdir($path) || die "can't cd into dir $path for import: $!";
216 log_system("$cvs import -d -m '$msg' $CVSREP/$path svn r$rev", "cvs import of $path failed");
217 chdir("$TMPDIR") || die "can't cd to $TMPDIR/$CVSREP: $!";
218 log_system("$cvs checkout $CVSREP/$path", "cvs checkout of imported dir $path failed");
219 chdir("$TMPDIR/$CVSREP") || die "can't cd back to $TMPDIR/$CVSREP: $!";
220 } elsif ($path =~ m,^(.+)/[^/]+$, && ! -e "$1/CVS/Root") {
221 my $dir = $1;
222 in_entries($dir) || log_system("$cvs add $dir", "cvs add of dir $dir failed");
223 in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");
224 } else {
225 in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");
226 }
227 } elsif ($action =~ /D/) {
228 unlink $path || die "can't delete $path: $!";
229 log_system("$cvs delete $path", "cvs delete of $path failed");
230 } else {
231 print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
232 }
233
234 # now commit changes
235 log_system("$cvs commit -m '$msg' $path", "cvs commit of $path failed");
236
237 }
238
239 commit_svnrev($rev);
240 }
241
242 __END__
243
244 =pod
245
246 =head1 NAME
247
248 svn2cvs - save subversion commits to (read-only) cvs repository
249
250 =head1 SYNOPSIS
251
252 ./svn2cvs.pl SVN_URL CVSROOT CVSREPOSITORY
253
254 Usage example (used to self-host this script):
255
256 ./svn2cvs.pl file:///home/dpavlin/private/svn/svn2cvs/trunk/ \
257 :pserver:dpavlin@cvs.tigris.org:/cvs svn2cvs/src
258
259 =head1 DESCRIPTION
260
261 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.
262
263 It's using F<.svnrev> file (which will be created on first run) in
264 B<CVSROOT/CVSREPOSITORY> to store last Subversion revision which was
265 committed into CVS.
266
267 One run will do following things:
268
269 =over 4
270
271 =item *
272 checkout B<CVSREPOSITORY> from B<CVSROOT> to temporary directory
273
274 =item *
275 check if F<.svnrev> file exists and create it if it doesn't
276
277 =item *
278 loop through all revisions from current in B<CVSROOT/CVSREPOSITORY> (using
279 F<.svnrev>) up to B<HEAD> (current one)
280
281 =over 5
282
283 =item *
284 checkout next Subversion revision from B<SVN_URL> over CVS checkout
285 temporary directory
286
287 =item *
288 make modification (add and/or delete) done in that revision
289
290 =item *
291 commit modification (added, deleted or modified files/dirs) while
292 preserving original message from CVS
293
294 =item *
295 update F<.svnrev> to match current revision
296
297 =back
298
299 =item *
300 cleanup temporary directory
301
302 =back
303
304 If checkout fails for some reason (e.g. flaky ssh connection), you will
305 still have valid CVS repository, so all you have to do is run B<svn2cvs.pl>
306 again.
307
308 =head1 WARNINGS
309
310 "Cheap" copy operations in Subversion are not at all cheap in CVS. They will
311 create multiple copies of files in CVS repository!
312
313 =head1 RELATED PROJECTS
314
315 B<Subversion> L<http://subversion.tigris.org/> version control system that is a
316 compelling replacement for CVS in the open source community.
317
318 B<cvs2svn> L<http://cvs2svn.tigris.org/> converts a CVS repository to a
319 Subversion repository. It is designed for one-time conversions, not for
320 repeated synchronizations between CVS and Subversion.
321
322 =head1 AUTHOR
323
324 Dobrica Pavlinusic <dpavlin@rot13.org>
325
326 L<https://www.rot13.org/~dpavlin/>
327
328 =head1 LICENSE
329
330 This product is licensed under GNU Public License (GPL) v2 or later.
331
332 =cut
333

Properties

Name Value
svn:executable

  ViewVC Help
Powered by ViewVC 1.1.26