/[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 23 by dpavlin, Tue Jan 3 00:03:22 2006 UTC revision 45 by dpavlin, Sun Sep 23 22:46:53 2007 UTC
# Line 17  use File::Path; Line 17  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  }  }
# Line 32  if ($SVNROOT !~ m,^[\w+]+:///*\w+,) { Line 38  if ($SVNROOT !~ m,^[\w+]+:///*\w+,) {
38  # Ensure File::Temp::END can clean up:  # Ensure File::Temp::END can clean up:
39  $SIG{__DIE__} = sub { chdir("/tmp"); die @_ };  $SIG{__DIE__} = sub { chdir("/tmp"); die @_ };
40    
41  my $TMPDIR=tempdir( "/tmp/checkoutXXXXX", CLEANUP => 1 );  my $TMPDIR = tempdir( "/tmp/checkoutXXXXX", CLEANUP => 1 );
42    
43  sub cd_tmp {  sub cd_tmp {
44          chdir($TMPDIR) || die "can't cd to $TMPDIR: $!";          chdir($TMPDIR) || die "can't cd to $TMPDIR: $!";
# Line 45  sub cd_rep { Line 51  sub cd_rep {
51  print "## using TMPDIR $TMPDIR\n";  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  # current revision in CVS
57  my $rev;  my $rev;
# Line 54  my $rev; Line 60  my $rev;
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 63  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  sub add_dir($$) {  sub add_dir($$) {
95          my ($path,$msg) = @_;          my ( $path, $msg ) = @_;
96          print "# add_dir($path)\n";          print "# add_dir($path)\n";
97          die "add_dir($path) is not directory" unless (-d $path);          die "add_dir($path) is not directory" unless ( -d $path );
98    
99          my $curr_dir;          my $curr_dir;
100    
101          foreach my $d (split(m#/#, $path)) {          foreach my $d ( split( m#/#, $path ) ) {
102                  $curr_dir .= ( $curr_dir ? '/' : '') . $d;                  $curr_dir .= ( $curr_dir ? '/' : '' ) . $d;
103    
104                  next if in_entries($curr_dir);                  next if in_entries($curr_dir);
105                  next if (-e "$curr_dir/CVS");                  next if ( -e "$curr_dir/CVS" );
106    
107                  log_system("$cvs add $curr_dir", "cvs add of $curr_dir failed");                  log_system( "$cvs add '$curr_dir'", "cvs add of $curr_dir failed" );
108          }          }
109  }  }
110    
111  # ok, now do the checkout  # ok, now do the checkout
112  eval {  eval {
113          cd_tmp;          cd_tmp;
114          log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");          log_system( "$cvs -q checkout $CVSREP", "cvs checkout failed" );
115  };  };
116    
117  if ($@) {  if ($@) {
# Line 122  _NEW_REP_ Line 130  _NEW_REP_
130          mkdir($CVSREP) || die "can't create $CVSREP: $!";          mkdir($CVSREP) || die "can't create $CVSREP: $!";
131          cd_rep;          cd_rep;
132    
133          open(SVNREV,"> .svnrev") || die "can't open $CVSREP/.svnrev: $!";          open( SVNREV, "> .svnrev" ) || die "can't open $CVSREP/.svnrev: $!";
134          print SVNREV "0";          print SVNREV "0";
135          close(SVNREV);          close(SVNREV);
136    
# Line 130  _NEW_REP_ Line 138  _NEW_REP_
138    
139          # create new module          # create new module
140          cd_rep;          cd_rep;
141          log_system("$cvs import -d -m 'new CVS module' $CVSREP svn r$rev", "import of new repository");          log_system( "$cvs import -d -m 'new CVS module' $CVSREP svn r$rev",
142                    "import of new repository" );
143          cd_tmp;          cd_tmp;
144          rmtree($CVSREP) || die "can't remove $CVSREP";          rmtree($CVSREP) || die "can't remove $CVSREP";
145          log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");          log_system( "$cvs -q checkout $CVSREP", "cvs checkout failed" );
146          cd_rep;          cd_rep;
147    
148  } else {  } else {
149    
150          # import into existing module directory in CVS          # import into existing module directory in CVS
151    
152          cd_rep;          cd_rep;
153    
154          # check if svnrev exists          # check if svnrev exists
155          if (! -e ".svnrev") {          if ( !-e ".svnrev" ) {
156                  print <<_USAGE_;                  print <<_USAGE_;
157    
158  Your CVS repository doesn't have .svnrev file!  Your CVS repository doesn't have .svnrev file!
# Line 163  _USAGE_ Line 174  _USAGE_
174                  print "svn revision corresponding to CVS [abort]: ";                  print "svn revision corresponding to CVS [abort]: ";
175                  my $in = <STDIN>;                  my $in = <STDIN>;
176                  chomp($in);                  chomp($in);
177                  if ($in !~ /^\d+$/) {                  if ( $in !~ /^\d+$/ ) {
178                          print "Aborting: revision not a number\n";                          print "Aborting: revision not a number\n";
179                          exit 1;                          exit 1;
180                  } else {                  } else {
181                          $rev = $in;                          $rev = $in;
182                          commit_svnrev($rev,1);  # create new                          commit_svnrev( $rev, 1 );    # create new
183                  }                  }
184          } else {          } else {
185                  open(SVNREV,".svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";                  open( SVNREV, ".svnrev" )
186                            || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
187                  $rev = <SVNREV>;                  $rev = <SVNREV>;
188                  chomp($rev);                  chomp($rev);
189                  close(SVNREV);                  close(SVNREV);
# Line 181  _USAGE_ Line 193  _USAGE_
193          $rev++;          $rev++;
194  }  }
195    
   
196  #  #
197  # 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
198  # loose multiple edits of same file and corresponding messages. On the  # loose multiple edits of same file and corresponding messages. On the
# Line 189  _USAGE_ Line 200  _USAGE_
200  # case much about accuracy and completnes of logs there, this might  # case much about accuracy and completnes of logs there, this might
201  # be good. YMMV  # be good. YMMV
202  #  #
203  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 |" )
204            || die "svn log for repository $SVNROOT failed: $!";
205  my $log;  my $log;
206  while(<LOG>) {  while (<LOG>) {
207          $log .= $_;          $log .= $_;
208  }  }
209  close(LOG);  close(LOG);
210    
   
211  my $xml;  my $xml;
212  eval {  eval { $xml = XMLin( $log, ForceArray => [ 'logentry', 'path' ] ); };
         $xml = XMLin($log, ForceArray => [ 'logentry', 'path' ]);  
 };  
   
213    
214  #=begin log_example  #=begin log_example
215  #  #
# Line 214  eval { Line 222  eval {
222    
223  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";
224    
225  if (! $xml->{'logentry'}) {  if ( !$xml->{'logentry'} ) {
226          print "no newer log entries in Subversion repostory. CVS is current\n";          print "no newer log entries in Subversion repostory. CVS is current\n";
227          exit 0;          exit 0;
228  }  }
229    
230    # return all files in CVS/Entries
231    sub entries($) {
232            my $dir = shift;
233            die "entries expects directory argument!" unless -d $dir;
234            my @entries;
235            open( my $fh, "./$dir/CVS/Entries" ) || return 0;
236            while (<$fh>) {
237                    if ( m{^D/([^/]+)}, ) {
238                            my $sub_dir = $1;
239                            warn "#### entries recurse into: $dir/$sub_dir";
240                            push @entries, map {"$sub_dir/$_"} entries("$dir/$sub_dir");
241                            push @entries, $sub_dir;
242                    } elsif (m{^/([^/]+)/}) {
243                            push @entries, $1;
244                    } elsif ( !m{^D$} ) {
245                            die "can't decode entries line: $_";
246                    }
247            }
248            close($fh);
249            warn "#### entries($dir) => ", join( "|", @entries );
250            return @entries;
251    }
252    
253  # check if file exists in CVS/Entries  # check if file exists in CVS/Entries
254  sub in_entries($) {  sub in_entries($) {
255          my $path = shift;          my $path = shift;
256          if ($path !~ m,^(.*?/*)([^/]+)$,) {          if ( $path =~ m,^(.*?/*)([^/]+)$, ) {
257                  die "can't split '$path' to dir and file!";                  my ( $dir, $file ) = ( $1, $2 );
258          } else {                  if ( $dir !~ m,/$, && $dir ne "" ) {
259                  my ($d,$f) = ($1,$2);                          $dir .= "/";
                 if ($d !~ m,/$, && $d ne "") {  
                         $d .= "/";  
260                  }                  }
261                  open(E, $d."CVS/Entries") || return 0;  
262                  while(<E>) {                  open( my $fh, "./$dir/CVS/Entries" )
263                          return(1) if (m,^/$f/,);                          || return 0;    #die "no entries file: $dir/CVS/Entries";
264                    while (<$fh>) {
265                            return 1 if (m{^/$file/});
266                  }                  }
267                  close(E);                  close($fh);
268                  return 0;                  return 0;
269            } else {
270                    die "can't split '$path' to dir and file!";
271          }          }
272  }  }
273    
274  cd_tmp;  cd_tmp;
275  cd_rep;  cd_rep;
276    
277  foreach my $e (@{$xml->{'logentry'}}) {  foreach my $e ( @{ $xml->{'logentry'} } ) {
278          die "BUG: revision from .svnrev ($rev) greater than from subversion (".$e->{'revision'}.")" if ($rev > $e->{'revision'});          die "BUG: revision from .svnrev ($rev) greater than from subversion ("
279                    . $e->{'revision'} . ")"
280                    if ( $rev > $e->{'revision'} );
281          $rev = $e->{'revision'};          $rev = $e->{'revision'};
282          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",
283                    "svn export of revision $rev failed" );
284    
285          # deduce name of svn directory          # deduce name of svn directory
286          my $SVNREP = "";          my $SVNREP  = "";
287          my $tmpsvn = $SVNROOT || die "BUG: SVNROOT empty!";          my $tmpsvn  = $SVNROOT || die "BUG: SVNROOT empty!";
288          my $tmppath = $e->{'paths'}->{'path'}->[0]->{'content'} || die "BUG: tmppath empty!";          my $tmppath = $e->{'paths'}->{'path'}->[0]->{'content'}
289                    || die "BUG: tmppath empty!";
290          do {          do {
291                  if ($tmpsvn =~ s#(/[^/]+)/*$##) {                  if ( $tmpsvn =~ s#(/[^/]+)/*$## ) {    # vim fix
292                          $SVNREP = $1 . $SVNREP;                          $SVNREP = $1 . $SVNREP;
293                    } elsif ( $e->{'paths'}->{'path'}->[0]->{'copyfrom-path'} ) {
294                            print
295                                    "NOTICE: copyfrom outside synced repository ignored - skipping\n";
296                            next;
297                  } else {                  } else {
298                          print "NOTICE: can't deduce svn dir from $SVNROOT - skipping\n";                          print "NOTICE: can't deduce svn dir from $SVNROOT - skipping\n";
299                          next;                          next;
300                  }                  }
301          } until ($tmppath =~ m/^$SVNREP/);          } until ( $tmppath =~ m/^$SVNREP/ );
302    
303          print "NOTICE: using $SVNREP as directory for svn\n";          print "NOTICE: using $SVNREP as directory for svn\n";
304    
305          printf($fmt, $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'});          printf( $fmt,
306                    $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'} );
307          my @commit;          my @commit;
308    
309          foreach my $p (@{$e->{'paths'}->{'path'}}) {          my $msg = $e->{'msg'};
310                  my ($action,$path) = ($p->{'action'},$p->{'content'});          $msg =~ s/'/'\\''/g;    # quote "
311    
312                  next if ($path =~ m#/\.svnrev$#);          $msg = 'r' . $rev . ' ' . $e->{author} . ' | ' . $e->{date} . "\n" . $msg
313                    if $decorate_commit_message;
314    
315            sub cvs_commit {
316                    my $msg = shift || die "no msg?";
317                    if ( !@_ ) {
318                            warn "commit ignored, no files\n";
319                            return;
320                    }
321                    log_system(
322                            "$cvs commit -m '$msg' '" . join( "' '", @_ ) . "'",
323                            "cvs commit of " . join( ",",            @_ ) . " failed"
324                    );
325            }
326    
327            foreach my $p ( @{ $e->{'paths'}->{'path'} } ) {
328                    my ( $action, $path ) = ( $p->{'action'}, $p->{'content'} );
329    
330                    next if ( $path =~ m#/\.svnrev$# );
331    
332                  print "svn2cvs: $action $path\n";                  print "svn2cvs: $action $path\n";
333    
334                  # prepare path and message                  # prepare path and message
335                  my $file = $path;                  my $file = $path;
336                  $path =~ s#^\Q$SVNREP\E/*## || die "BUG: can't strip SVNREP '$SVNREP' from path";                  if ( $path !~ s#^\Q$SVNREP\E/*## ) {
337                            print
338                                    "NOTICE: skipping '$path' which isn't under repository root '$SVNREP'\n";
339                            die unless $partial_import;
340                            next;
341                    }
342    
343                  if (! $path) {                  if ( !$path ) {
344                          print "NOTICE: skipped this operation. Probably trunk creation\n";                          print "NOTICE: skipped this operation. Probably trunk creation\n";
345                          next;                          next;
346                  }                  }
347    
348                  my $msg = $e->{'msg'};                  my $msg = $e->{'msg'};
349                  $msg =~ s/'/'\\''/g;    # quote "                  $msg =~ s/'/'\\''/g;    # quote "
350    
351                    sub add_path {
352                            my $path = shift || die "no path?";
353    
354                  if ($action =~ /M/) {                          if ( -d $path ) {
355                          print "svn2cvs: modify $path -- nop\n";                                  add_dir( $path, $msg );
356                  } elsif ($action =~ /A/) {                          } elsif ( $path =~ m,^(.+)/[^/]+$, && !-e "$1/CVS/Root" ) {
                         if (-d $path) {  
                                 add_dir($path, $msg);  
                         } elsif ($path =~ m,^(.+)/[^/]+$, && ! -e "$1/CVS/Root") {  
357                                  my $dir = $1;                                  my $dir = $1;
358                                  in_entries($dir) || add_dir($dir, $msg);                                  in_entries($dir) || add_dir( $dir, $msg );
359                                  in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");                                  in_entries($path) || log_system( "$cvs add '$path'",
360                                            "cvs add of $path failed" );
361                            } else {
362                                    in_entries($path) || log_system( "$cvs add '$path'",
363                                            "cvs add of $path failed" );
364                            }
365                    }
366    
367                    if ( $action =~ /M/ ) {
368                            if ( in_entries($path) ) {
369                                    print "svn2cvs: modify $path -- nop\n";
370                          } else {                          } else {
371                                  in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");                                  print "WARNING: modify $path which isn't in CVS, adding...\n";
372                                    add_path($path);
373                          }                          }
374                  } elsif ($action =~ /D/) {                  } elsif ( $action =~ /A/ ) {
375                          if (-e $path) {                          add_path($path);
376                                  unlink $path || die "can't delete $path: $!";                  } elsif ( $action =~ /D/ ) {
377                                  log_system("$cvs delete $path", "cvs delete of $path failed");                          if ( -e $path ) {
378                                    if ( ! in_entries( $path ) ) {
379                                            print "WARNING: $path is not present in CVS, skipping...\n";
380                                            undef $path;
381                                    } elsif ( -d $path ) {
382                                            warn "#### remove directory: $path";
383                                            foreach my $f ( entries($path) ) {
384                                                    $f = "$path/$f";
385                                                    if ( -f $f ) {
386                                                            unlink($f) || die "can't delete file $f: $!";
387    
388                                                      #                                             } else {
389                                                      #                                                     rmtree($f) || die "can't delete dir $f: $!";
390                                                    }
391                                                    log_system( "$cvs delete '$f'",
392                                                            "cvs delete of file $f failed" );
393                                                    cvs_commit( $msg, $f );
394                                            }
395                                            log_system( "$cvs delete '$path'",
396                                                    "cvs delete of file $path failed" );
397                                            cvs_commit( $msg, $path );
398                                            log_system( "$cvs update -dP .",
399                                                    "cvs update -dP . failed" );
400                                            undef $path;
401                                    } else {
402                                            warn "#### remove file: $path";
403                                            unlink($path) || die "can't delete $path: $!";
404                                            log_system( "$cvs delete '$path'",
405                                                    "cvs delete of dir $path failed" );
406                                            cvs_commit( $msg, $path );
407                                            undef $path;
408                                    }
409                          } else {                          } else {
410                                  print "WARNING: $path is not present, skipping...\n";                                  print "WARNING: $path is not present, skipping...\n";
411                                  undef $path;                                  undef $path;
412                          }                          }
413                  } else {                  } else {
414                          print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";                          print
415                                    "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
416                  }                  }
417    
418                  # save commits for later                  # save commits for later
# Line 312  foreach my $e (@{$xml->{'logentry'}}) { Line 420  foreach my $e (@{$xml->{'logentry'}}) {
420    
421          }          }
422    
         my $msg = $e->{'msg'};  
         $msg =~ s/'/'\\''/g;    # quote "  
   
423          # now commit changes          # now commit changes
424          log_system("$cvs commit -m '$msg' ".join(" ",@commit), "cvs commit of ".join(",",@commit)." failed");          cvs_commit( $msg, @commit );
425    
426          commit_svnrev($rev);          commit_svnrev($rev);
427  }  }

Legend:
Removed from v.23  
changed lines
  Added in v.45

  ViewVC Help
Powered by ViewVC 1.1.26