/[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 12 - (show 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 #!/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 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 log_system("svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP", "svn export of revision $rev failed");
158
159 # 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 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 $path =~ s,^$SVNREP/*,, || die "BUG: can't strip SVNREP from path";
183
184 if (! $path) {
185 print "NOTICE: skipped this operation. Probably trunk creation\n";
186 next;
187 }
188
189 my $msg = $e->{'msg'};
190 $msg =~ s/'/\\'/g; # quote "
191
192 if ($action =~ /M/) {
193 print "svn2cvs: modify $path -- nop\n";
194 } elsif ($action =~ /A/) {
195 if (-d $path) {
196 chdir($path) || die "can't cd into dir $path for import: $!";
197 log_system("$cvs import -d -m '$msg' $CVSREP/$path svn r$rev", "cvs import of $path failed");
198 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 log_system("$cvs add -m '$msg' $path", "cvs add of $path failed");
203 }
204 } elsif ($action =~ /D/) {
205 log_system("$cvs delete -m '$msg' $path", "cvs delete of $path failed");
206 } else {
207 print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
208 }
209
210 # now commit changes
211 log_system("$cvs commit -m '$msg' $path", "cvs commit of $path failed");
212
213 }
214
215 commit_svnrev($rev);
216 }
217
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