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

Diff of /trunk/svn2cvs.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 20 by dpavlin, Mon Apr 18 16:43:08 2005 UTC revision 47 by dpavlin, Fri Nov 2 12:11:31 2007 UTC
# Line 13  Line 13 
13    
14  use strict;  use strict;
15  use File::Temp qw/ tempdir /;  use File::Temp qw/ tempdir /;
16    use File::Path;
17  use Data::Dumper;  use Data::Dumper;
18  use XML::Simple;  use XML::Simple;
19    
20  if (@ARGV < 2) {  # do we want to sync just part of repository?
21    my $partial_import = 1;
22    
23    # do we want to add svk-like prefix with original revision, author and date?
24    my $decorate_commit_message = 1;
25    
26    if ( @ARGV < 2 ) {
27          print "usage: $0 SVN_URL CVSROOT CVSREPOSITORY\n";          print "usage: $0 SVN_URL CVSROOT CVSREPOSITORY\n";
28          exit 1;          exit 1;
29  }  }
30    
31  my ($SVNROOT,$CVSROOT, $CVSREP) = @ARGV;  my ( $SVNROOT, $CVSROOT, $CVSREP ) = @ARGV;
32    
33  if ($SVNROOT !~ m,^[\w+]+:///*\w+,) {  if ( $SVNROOT !~ m,^[\w+]+:///*\w+, ) {
34          print "ERROR: invalid svn root $SVNROOT\n";          print "ERROR: invalid svn root $SVNROOT\n";
35          exit 1;          exit 1;
36  }  }
37    
38  my $TMPDIR=tempdir( "/tmp/checkoutXXXXX", CLEANUP => 1 );  # Ensure File::Temp::END can clean up:
39    $SIG{__DIE__} = sub { chdir("/tmp"); die @_ };
40    
41    my $TMPDIR = tempdir( "/tmp/checkoutXXXXX", CLEANUP => 1 );
42    
43    sub cd_tmp {
44            chdir($TMPDIR) || die "can't cd to $TMPDIR: $!";
45    }
46    
47    sub cd_rep {
48            chdir("$TMPDIR/$CVSREP") || die "can't cd to $TMPDIR/$CVSREP: $!";
49    }
50    
51  chdir($TMPDIR) || die "can't cd to $TMPDIR: $!";  print "## using TMPDIR $TMPDIR\n";
52    
53  # cvs command with root  # cvs command with root
54  my $cvs="cvs -d $CVSROOT";  my $cvs = "cvs -f -d $CVSROOT";
55    
56    # current revision in CVS
57    my $rev;
58    
59  #  #
60  # sub to do logging and system calls  # sub to do logging and system calls
61  #  #
62  sub log_system($$) {  sub log_system($$) {
63          my ($cmd,$errmsg) = @_;          my ( $cmd, $errmsg ) = @_;
64          print STDERR "## $cmd\n";          print STDERR "## $cmd\n";
65          system($cmd) == 0 || die "$errmsg: $!";          system($cmd) == 0 || die "$errmsg: $!";
66  }  }
# Line 48  sub log_system($$) { Line 69  sub log_system($$) {
69  # sub to commit .svn rev file later  # sub to commit .svn rev file later
70  #  #
71  sub commit_svnrev {  sub commit_svnrev {
72          my $rev = shift @_;          my $rev     = shift @_;
73          my $add_new = shift @_;          my $add_new = shift @_;
74    
75          die "commit_svnrev needs revision" if (! defined($rev));          die "commit_svnrev needs revision" if ( !defined($rev) );
76    
77          open(SVNREV,"> .svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";          open( SVNREV, "> .svnrev" )
78                    || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
79          print SVNREV $rev;          print SVNREV $rev;
80          close(SVNREV);          close(SVNREV);
81    
82          my $path=".svnrev";          my $path = ".svnrev";
83    
84          if ($add_new) {          if ($add_new) {
85                  system "$cvs add $path" || die "cvs add of $path failed: $!";                  system "$cvs add '$path'" || die "cvs add of $path failed: $!";
86          } else {          } else {
87                  my $msg="subversion revision $rev commited to CVS";                  my $msg = "subversion revision $rev commited to CVS";
88                  print "$msg\n";                  print "$msg\n";
89                  system "$cvs commit -m '$msg' $path" || die "cvs commit of $path failed: $!";                  system "$cvs commit -m '$msg' '$path'"
90                            || die "cvs commit of $path failed: $!";
91          }          }
92  }  }
93    
94  # current revision in CVS  sub add_dir($$) {
95  my $rev;          my ( $path, $msg ) = @_;
96            print "# add_dir($path)\n";
97            die "add_dir($path) is not directory" unless ( -d $path );
98    
99            my $curr_dir;
100    
101            foreach my $d ( split( m#/#, $path ) ) {
102                    $curr_dir .= ( $curr_dir ? '/' : '' ) . $d;
103    
104                    next if in_entries($curr_dir);
105                    next if ( -e "$curr_dir/CVS" );
106    
107                    log_system( "touch '$curr_dir/.keepme'", "creation of .keepme file (to keep $curr_dir alive in CVS) failed" );
108                    log_system( "$cvs add '$curr_dir' '$curr_dir/.keepme'", "cvs add of $curr_dir failed" );
109            }
110    }
111    
112  # ok, now do the checkout  # ok, now do the checkout
113  eval {  eval {
114          log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");          cd_tmp;
115            log_system( "$cvs -q checkout $CVSREP", "cvs checkout failed" );
116  };  };
117    
118  if ($@) {  if ($@) {
# Line 88  _NEW_REP_ Line 127  _NEW_REP_
127    
128          print "start import of new module [yes]: ";          print "start import of new module [yes]: ";
129          my $in = <STDIN>;          my $in = <STDIN>;
130            cd_tmp;
131          mkdir($CVSREP) || die "can't create $CVSREP: $!";          mkdir($CVSREP) || die "can't create $CVSREP: $!";
132            cd_rep;
133    
134          chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";          open( SVNREV, "> .svnrev" ) || die "can't open $CVSREP/.svnrev: $!";
   
         open(SVNREV,"> .svnrev") || die "can't open $CVSREP/.svnrev: $!";  
135          print SVNREV "0";          print SVNREV "0";
136          close(SVNREV);          close(SVNREV);
137    
138          $rev = 0;          $rev = 0;
139    
140          # create new module          # create new module
141          log_system("$cvs import -m 'new CVS module' $CVSREP svn2cvs r0", "can't import new module into $CVSREP");          cd_rep;
142            log_system( "$cvs import -d -m 'new CVS module' $CVSREP svn r$rev",
143          unlink ".svnrev" || die "can't remove .svnrev: $!";                  "import of new repository" );
144          chdir($TMPDIR) || die "can't cd to $TMPDIR: $!";          cd_tmp;
145          rmdir $CVSREP || die "can't remove $CVSREP: $!";          rmtree($CVSREP) || die "can't remove $CVSREP";
146            log_system( "$cvs -q checkout $CVSREP", "cvs checkout failed" );
147          # and checkout it          cd_rep;
         log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");  
   
         chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";  
148    
149  } else {  } else {
150    
151          # import into existing module directory in CVS          # import into existing module directory in CVS
152    
153          chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";          cd_rep;
   
154    
155          # check if svnrev exists          # check if svnrev exists
156          if (! -e ".svnrev") {          if ( !-e ".svnrev" ) {
157                  print <<_USAGE_;                  print <<_USAGE_;
158    
159  Your CVS repository doesn't have .svnrev file!  Your CVS repository doesn't have .svnrev file!
# Line 140  _USAGE_ Line 175  _USAGE_
175                  print "svn revision corresponding to CVS [abort]: ";                  print "svn revision corresponding to CVS [abort]: ";
176                  my $in = <STDIN>;                  my $in = <STDIN>;
177                  chomp($in);                  chomp($in);
178                  if ($in !~ /^\d+$/) {                  if ( $in !~ /^\d+$/ ) {
179                          print "Aborting: revision not a number\n";                          print "Aborting: revision not a number\n";
180                          exit 1;                          exit 1;
181                  } else {                  } else {
182                          $rev = $in;                          $rev = $in;
183                          commit_svnrev($rev,1);  # create new                          commit_svnrev( $rev, 1 );    # create new
184                  }                  }
185          } else {          } else {
186                  open(SVNREV,".svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";                  open( SVNREV, ".svnrev" )
187                            || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
188                  $rev = <SVNREV>;                  $rev = <SVNREV>;
189                  chomp($rev);                  chomp($rev);
190                  close(SVNREV);                  close(SVNREV);
# Line 158  _USAGE_ Line 194  _USAGE_
194          $rev++;          $rev++;
195  }  }
196    
   
197  #  #
198  # FIXME!! HEAD should really be next verison and loop because this way we  # FIXME!! HEAD should really be next verison and loop because this way we
199  # loose multiple edits of same file and corresponding messages. On the  # loose multiple edits of same file and corresponding messages. On the
# Line 166  _USAGE_ Line 201  _USAGE_
201  # case much about accuracy and completnes of logs there, this might  # case much about accuracy and completnes of logs there, this might
202  # be good. YMMV  # be good. YMMV
203  #  #
204  open(LOG, "svn log -r $rev:HEAD -v --xml $SVNROOT |") || die "svn log for repository $SVNROOT failed: $!";  open( LOG, "svn log -r $rev:HEAD -v --xml $SVNROOT |" )
205            || die "svn log for repository $SVNROOT failed: $!";
206  my $log;  my $log;
207  while(<LOG>) {  while (<LOG>) {
208          $log .= $_;          $log .= $_;
209  }  }
210  close(LOG);  close(LOG);
211    
212  my $xml = XMLin($log, ForceArray => [ 'logentry', 'path' ]);  my $xml;
213    eval { $xml = XMLin( $log, ForceArray => [ 'logentry', 'path' ] ); };
214    
215  #=begin log_example  #=begin log_example
216  #  #
# Line 187  my $xml = XMLin($log, ForceArray => [ 'l Line 223  my $xml = XMLin($log, ForceArray => [ 'l
223    
224  my $fmt = "\n" . "-" x 79 . "\nr%5s| %8s | %s\n\n%s\n";  my $fmt = "\n" . "-" x 79 . "\nr%5s| %8s | %s\n\n%s\n";
225    
226  if (! $xml->{'logentry'}) {  if ( !$xml->{'logentry'} ) {
227          print "no newer log entries in Subversion repostory. CVS is current\n";          print "no newer log entries in Subversion repostory. CVS is current\n";
228          exit 0;          exit 0;
229  }  }
230    
231    # return all files in CVS/Entries
232    sub entries($) {
233            my $dir = shift;
234            die "entries expects directory argument!" unless -d $dir;
235            my @entries;
236            open( my $fh, "./$dir/CVS/Entries" ) || return 0;
237            while (<$fh>) {
238                    if ( m{^D/([^/]+)}, ) {
239                            my $sub_dir = $1;
240                            warn "#### entries recurse into: $dir/$sub_dir";
241                            push @entries, map {"$sub_dir/$_"} entries("$dir/$sub_dir");
242                            push @entries, $sub_dir;
243                    } elsif (m{^/([^/]+)/}) {
244                            push @entries, $1;
245                    } elsif ( !m{^D$} ) {
246                            die "can't decode entries line: $_";
247                    }
248            }
249            close($fh);
250            warn "#### entries($dir) => ", join( "|", @entries );
251            return @entries;
252    }
253    
254  # check if file exists in CVS/Entries  # check if file exists in CVS/Entries
255  sub in_entries($) {  sub in_entries($) {
256          my $path = shift;          my $path = shift;
257          if ($path !~ m,^(.*?/*)([^/]+)$,) {          if ( $path =~ m,^(.*?/*)([^/]+)$, ) {
258                  die "can't split '$path' to dir and file!";                  my ( $dir, $file ) = ( $1, $2 );
259          } else {                  if ( $dir !~ m,/$, && $dir ne "" ) {
260                  my ($d,$f) = ($1,$2);                          $dir .= "/";
                 if ($d !~ m,/$, && $d ne "") {  
                         $d .= "/";  
261                  }                  }
262                  open(E, $d."CVS/Entries") || die "can't open ${d}CVS/Entries: $!";  
263                  while(<E>) {                  open( my $fh, "./$dir/CVS/Entries" )
264                          return(1) if (m,^/$f/,);                          || return 0;    #die "no entries file: $dir/CVS/Entries";
265                    while (<$fh>) {
266                            return 1 if (m{^D?/$file/});
267                  }                  }
268                  close(E);                  close($fh);
269                  return 0;                  return 0;
270            } else {
271                    die "can't split '$path' to dir and file!";
272          }          }
273  }  }
274    
275  foreach my $e (@{$xml->{'logentry'}}) {  cd_tmp;
276          die "BUG: revision from .svnrev ($rev) greater than from subversion (".$e->{'revision'}.")" if ($rev > $e->{'revision'});  cd_rep;
277    
278    foreach my $e ( @{ $xml->{'logentry'} } ) {
279            die "BUG: revision from .svnrev ($rev) greater than from subversion ("
280                    . $e->{'revision'} . ")"
281                    if ( $rev > $e->{'revision'} );
282          $rev = $e->{'revision'};          $rev = $e->{'revision'};
283          log_system("svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP", "svn export of revision $rev failed");          log_system( "svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP",
284                    "svn export of revision $rev failed" );
285    
286          # deduce name of svn directory          # deduce name of svn directory
287          my $SVNREP = "";          my $SVNREP  = "";
288          my $tmpsvn = $SVNROOT || die "BUG: SVNROOT empty!";          my $tmpsvn  = $SVNROOT || die "BUG: SVNROOT empty!";
289          my $tmppath = $e->{'paths'}->{'path'}->[0]->{'content'} || die "BUG: tmppath empty!";          my $tmppath = $e->{'paths'}->{'path'}->[0]->{'content'}
290                    || die "BUG: tmppath empty!";
291          do {          do {
292                  if ($tmpsvn =~ s#(/[^/]+)/*$##) {                  if ( $tmpsvn =~ s#(/[^/]+)/*$## ) {    # vim fix
293                          $SVNREP = $1 . $SVNREP;                          $SVNREP = $1 . $SVNREP;
294                    } elsif ( $e->{'paths'}->{'path'}->[0]->{'copyfrom-path'} ) {
295                            print
296                                    "NOTICE: copyfrom outside synced repository ignored - skipping\n";
297                            next;
298                  } else {                  } else {
299                          print "NOTICE: can't deduce svn dir from $SVNROOT - skipping\n";                          print "NOTICE: can't deduce svn dir from $SVNROOT - skipping\n";
300                          next;                          next;
301                  }                  }
302          } until ($tmppath =~ m/^$SVNREP/);          } until ( $tmppath =~ m/^$SVNREP/ );
303    
304          print "NOTICE: using $SVNREP as directory for svn\n";          print "NOTICE: using $SVNREP as directory for svn\n";
305    
306          printf($fmt, $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'});          printf( $fmt,
307                    $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'} );
308          my @commit;          my @commit;
309    
310          foreach my $p (@{$e->{'paths'}->{'path'}}) {          my $msg = $e->{'msg'};
311                  my ($action,$path) = ($p->{'action'},$p->{'content'});          $msg =~ s/'/'\\''/g;    # quote "
312    
313            $msg = 'r' . $rev . ' ' . $e->{author} . ' | ' . $e->{date} . "\n" . $msg
314                    if $decorate_commit_message;
315    
316            sub cvs_commit {
317                    my $msg = shift || die "no msg?";
318                    if ( !@_ ) {
319                            warn "commit ignored, no files\n";
320                            return;
321                    }
322                    log_system(
323                            "$cvs commit -m '$msg' '" . join( "' '", @_ ) . "'",
324                            "cvs commit of " . join( ",",            @_ ) . " failed"
325                    );
326            }
327    
328            foreach my $p ( @{ $e->{'paths'}->{'path'} } ) {
329                    my ( $action, $path ) = ( $p->{'action'}, $p->{'content'} );
330    
331                    next if ( $path =~ m#/\.svnrev$# );
332    
333                  print "svn2cvs: $action $path\n";                  print "svn2cvs: $action $path\n";
334    
335                  # prepare path and message                  # prepare path and message
336                  my $file = $path;                  my $file = $path;
337                  $path =~ s,^$SVNREP/*,, || die "BUG: can't strip SVNREP '$SVNREP' from path";                  if ( $path !~ s#^\Q$SVNREP\E/*## ) {
338                            print
339                                    "NOTICE: skipping '$path' which isn't under repository root '$SVNREP'\n";
340                            die unless $partial_import;
341                            next;
342                    }
343    
344                  if (! $path) {                  if ( !$path ) {
345                          print "NOTICE: skipped this operation. Probably trunk creation\n";                          print "NOTICE: skipped this operation. Probably trunk creation\n";
346                          next;                          next;
347                  }                  }
348    
349                  my $msg = $e->{'msg'};                  my $msg = $e->{'msg'};
350                  $msg =~ s/'/'\\''/g;    # quote "                  $msg =~ s/'/'\\''/g;    # quote "
351    
352                    sub add_path {
353                            my $path = shift || die "no path?";
354    
355                  if ($action =~ /M/) {                          if ( -d $path ) {
356                          print "svn2cvs: modify $path -- nop\n";                                  add_dir( $path, $msg );
357                  } elsif ($action =~ /A/) {                          } elsif ( $path =~ m,^(.+)/[^/]+$, && !-e "$1/CVS/Root" ) {
358                          if (-d $path) {                                  my $dir = $1;
359                                  chdir($path) || die "can't cd into dir $path for import: $!";                                  in_entries($dir) || add_dir( $dir, $msg );
360                                  log_system("$cvs import -d -m '$msg' $CVSREP/$path svn r$rev", "cvs import of $path failed");                                  in_entries($path) || log_system( "$cvs add '$path'",
361                                  if (-d "$CVSREP/$path") {                                          "cvs add of $path failed" );
362                                          rmdir "$CVSREP/$path" || die "can't remove $CVSREP/$path: $!";                          } else {
363                                    in_entries($path) || log_system( "$cvs add '$path'",
364                                            "cvs add of $path failed" );
365                            }
366                    }
367    
368                    if ( $action =~ /M/ ) {
369                            if ( in_entries($path) ) {
370                                    print "svn2cvs: modify $path -- nop\n";
371                            } else {
372                                    print "WARNING: modify $path which isn't in CVS, adding...\n";
373                                    add_path($path);
374                            }
375                    } elsif ( $action =~ /A/ ) {
376                            add_path($path);
377                    } elsif ( $action =~ /D/ ) {
378                            if ( -e $path ) {
379                                    if ( ! in_entries( $path ) ) {
380                                            print "WARNING: $path is not present in CVS, skipping...\n";
381                                            undef $path;
382                                    } elsif ( -d $path ) {
383                                            warn "#### remove directory: $path";
384                                            foreach my $f ( entries($path) ) {
385                                                    $f = "$path/$f";
386                                                    if ( -f $f ) {
387                                                            unlink($f) || die "can't delete file $f: $!";
388    
389                                                      #                                             } else {
390                                                      #                                                     rmtree($f) || die "can't delete dir $f: $!";
391                                                    }
392                                                    log_system( "$cvs delete '$f'",
393                                                            "cvs delete of file $f failed" );
394                                                    cvs_commit( $msg, $f );
395                                            }
396                                            log_system( "$cvs delete '$path'",
397                                                    "cvs delete of file $path failed" );
398                                            cvs_commit( $msg, $path );
399                                            log_system( "$cvs update -dP .",
400                                                    "cvs update -dP . failed" );
401                                            undef $path;
402                                  } else {                                  } else {
403                                          unlink "$CVSREP/$path" || die "can't remove $CVSREP/$path: $!";                                          warn "#### remove file: $path";
404                                            unlink($path) || die "can't delete $path: $!";
405                                            log_system( "$cvs delete '$path'",
406                                                    "cvs delete of dir $path failed" );
407                                            cvs_commit( $msg, $path );
408                                            undef $path;
409                                  }                                  }
                                 chdir("$TMPDIR") || die "can't cd to $TMPDIR/$CVSREP: $!";  
                                 log_system("$cvs checkout $CVSREP/$path", "cvs checkout of imported dir $path failed");  
                                 chdir("$TMPDIR/$CVSREP") || die "can't cd back to $TMPDIR/$CVSREP: $!";  
                         } elsif ($path =~ m,^(.+)/[^/]+$, && ! -e "$1/CVS/Root") {  
                                 my $dir = $1;  
                                 in_entries($dir) || log_system("$cvs add $dir", "cvs add of dir $dir failed");  
                                 in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");  
410                          } else {                          } else {
411                                  in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");                                  print "WARNING: $path is not present, skipping...\n";
412                                    undef $path;
413                          }                          }
                 } elsif ($action =~ /D/) {  
                         unlink $path || die "can't delete $path: $!";  
                         log_system("$cvs delete $path", "cvs delete of $path failed");  
414                  } else {                  } else {
415                          print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";                          print
416                                    "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
417                  }                  }
418    
419                  # save commits for later                  # save commits for later
420                  push @commit, $path;                  push @commit, $path if ($path);
421    
422          }          }
423    
         my $msg = $e->{'msg'};  
         $msg =~ s/'/'\\''/g;    # quote "  
   
424          # now commit changes          # now commit changes
425          log_system("$cvs commit -m '$msg' ".join(" ",@commit), "cvs commit of ".join(",",@commit)." failed");          cvs_commit( $msg, @commit );
426    
427          commit_svnrev($rev);          commit_svnrev($rev);
428  }  }
429    
430    # cd out of $CVSREP before File::Temp::END is called
431    chdir("/tmp") || die "can't cd to /tmp: $!";
432    
433  __END__  __END__
434    
435  =pod  =pod
# Line 408  Documentation improvements and other sma Line 545  Documentation improvements and other sma
545    
546  Fixed path deduction (overlap between Subversion reporistory and CVS checkout).  Fixed path deduction (overlap between Subversion reporistory and CVS checkout).
547    
548    =item r21
549    
550    Use C<update -d> instead of checkout after import.
551    Added fixes by Paul Egan <paulegan@mail.com> for XMLin and fixing working
552    directory.
553    
554    =item r22
555    
556    Rewritten import from revision 0 to empty repository, better importing
557    of deep directory structures, initial support for recovery from partial
558    commit.
559    
560  =back  =back
561    
562  =head1 AUTHOR  =head1 AUTHOR

Legend:
Removed from v.20  
changed lines
  Added in v.47

  ViewVC Help
Powered by ViewVC 1.1.26