/[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 16 - (hide annotations)
Mon Mar 15 12:21:32 2004 UTC (20 years, 1 month ago) by dpavlin
File MIME type: text/plain
File size: 9074 byte(s)
added changes to 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 14 # 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 dpavlin 1 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 dpavlin 8 log_system("svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP", "svn export of revision $rev failed");
177 dpavlin 1
178 dpavlin 8 # 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     if ($tmpsvn =~ s,(/\w+/*)$,,) {
184     $SVNREP .= $1;
185     } else {
186 dpavlin 15 print "NOTICE: can't deduce svn dir from $SVNROOT - skipping\n";
187     next;
188 dpavlin 8 }
189     } until ($tmppath =~ m/^$SVNREP/);
190    
191     print "NOTICE: using $SVNREP as directory for svn\n";
192    
193 dpavlin 1 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 dpavlin 8 $path =~ s,^$SVNREP/*,, || die "BUG: can't strip SVNREP from path";
202 dpavlin 7
203     if (! $path) {
204     print "NOTICE: skipped this operation. Probably trunk creation\n";
205     next;
206     }
207    
208 dpavlin 1 my $msg = $e->{'msg'};
209 dpavlin 14 $msg =~ s/'/'\\''/g; # quote "
210 dpavlin 1
211     if ($action =~ /M/) {
212     print "svn2cvs: modify $path -- nop\n";
213     } elsif ($action =~ /A/) {
214 dpavlin 8 if (-d $path) {
215     chdir($path) || die "can't cd into dir $path for import: $!";
216 dpavlin 11 log_system("$cvs import -d -m '$msg' $CVSREP/$path svn r$rev", "cvs import of $path failed");
217 dpavlin 8 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 dpavlin 14 } 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 dpavlin 8 } else {
225 dpavlin 14 in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");
226 dpavlin 8 }
227 dpavlin 1 } elsif ($action =~ /D/) {
228 dpavlin 14 unlink $path || die "can't delete $path: $!";
229     log_system("$cvs delete $path", "cvs delete of $path failed");
230 dpavlin 1 } else {
231     print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
232     }
233    
234     # now commit changes
235 dpavlin 11 log_system("$cvs commit -m '$msg' $path", "cvs commit of $path failed");
236 dpavlin 1
237     }
238    
239     commit_svnrev($rev);
240     }
241 dpavlin 12
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 dpavlin 16 =head1 CHANGES
323    
324     Versions of this utility are actually Subversion repository revisions,
325     so they might not be in sequence.
326    
327     =over 3
328    
329     =item r10
330    
331     First release available to public
332    
333     =item r15
334    
335     Addition of comprehensive documentation, fixes for quoting in commit
336     messages, and support for skipping changes which are not under current
337     Subversion checkout root (e.g. branches).
338    
339     =back
340    
341 dpavlin 12 =head1 AUTHOR
342    
343     Dobrica Pavlinusic <dpavlin@rot13.org>
344    
345     L<https://www.rot13.org/~dpavlin/>
346    
347     =head1 LICENSE
348    
349     This product is licensed under GNU Public License (GPL) v2 or later.
350    
351     =cut
352    

Properties

Name Value
svn:executable

  ViewVC Help
Powered by ViewVC 1.1.26