/[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 27 by dpavlin, Thu Aug 16 15:27:09 2007 UTC revision 47 by dpavlin, Fri Nov 2 12:11:31 2007 UTC
# Line 20  use XML::Simple; Line 20  use XML::Simple;
20  # do we want to sync just part of repository?  # do we want to sync just part of repository?
21  my $partial_import = 1;  my $partial_import = 1;
22    
23  if (@ARGV < 2) {  # 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 35  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 48  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 57  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 66  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( "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          cd_tmp;          cd_tmp;
115          log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");          log_system( "$cvs -q checkout $CVSREP", "cvs checkout failed" );
116  };  };
117    
118  if ($@) {  if ($@) {
# Line 125  _NEW_REP_ Line 131  _NEW_REP_
131          mkdir($CVSREP) || die "can't create $CVSREP: $!";          mkdir($CVSREP) || die "can't create $CVSREP: $!";
132          cd_rep;          cd_rep;
133    
134          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    
# Line 133  _NEW_REP_ Line 139  _NEW_REP_
139    
140          # create new module          # create new module
141          cd_rep;          cd_rep;
142          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",
143                    "import of new repository" );
144          cd_tmp;          cd_tmp;
145          rmtree($CVSREP) || die "can't remove $CVSREP";          rmtree($CVSREP) || die "can't remove $CVSREP";
146          log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");          log_system( "$cvs -q checkout $CVSREP", "cvs checkout failed" );
147          cd_rep;          cd_rep;
148    
149  } else {  } else {
150    
151          # import into existing module directory in CVS          # import into existing module directory in CVS
152    
153          cd_rep;          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 166  _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 184  _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 192  _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;  my $xml;
213  eval {  eval { $xml = XMLin( $log, ForceArray => [ 'logentry', 'path' ] ); };
         $xml = XMLin($log, ForceArray => [ 'logentry', 'path' ]);  
 };  
   
214    
215  #=begin log_example  #=begin log_example
216  #  #
# Line 217  eval { Line 223  eval {
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") || return 0;  
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  cd_tmp;  cd_tmp;
276  cd_rep;  cd_rep;
277    
278  foreach my $e (@{$xml->{'logentry'}}) {  foreach my $e ( @{ $xml->{'logentry'} } ) {
279          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 ("
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'}) {                  } elsif ( $e->{'paths'}->{'path'}->[0]->{'copyfrom-path'} ) {
295                          print "NOTICE: copyfrom outside synced repository ignored - skipping\n";                          print
296                                    "NOTICE: copyfrom outside synced repository ignored - skipping\n";
297                          next;                          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$#);                  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                  if ( $path !~ s#^\Q$SVNREP\E/*## ) {                  if ( $path !~ s#^\Q$SVNREP\E/*## ) {
338                          print "NOTICE: skipping '$path' which isn't under repository root '$SVNREP'\n";                          print
339                                    "NOTICE: skipping '$path' which isn't under repository root '$SVNREP'\n";
340                          die unless $partial_import;                          die unless $partial_import;
341                          next;                          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                  if ($action =~ /M/) {                  sub add_path {
353                          print "svn2cvs: modify $path -- nop\n";                          my $path = shift || die "no path?";
354                  } elsif ($action =~ /A/) {  
355                          if (-d $path) {                          if ( -d $path ) {
356                                  add_dir($path, $msg);                                  add_dir( $path, $msg );
357                          } elsif ($path =~ m,^(.+)/[^/]+$, && ! -e "$1/CVS/Root") {                          } elsif ( $path =~ m,^(.+)/[^/]+$, && !-e "$1/CVS/Root" ) {
358                                  my $dir = $1;                                  my $dir = $1;
359                                  in_entries($dir) || add_dir($dir, $msg);                                  in_entries($dir) || add_dir( $dir, $msg );
360                                  in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");                                  in_entries($path) || log_system( "$cvs add '$path'",
361                                            "cvs add of $path failed" );
362                          } else {                          } else {
363                                  in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");                                  in_entries($path) || log_system( "$cvs add '$path'",
364                                            "cvs add of $path failed" );
365                          }                          }
366                  } elsif ($action =~ /D/) {                  }
367                          if (-e $path) {  
368                                  unlink $path || die "can't delete $path: $!";                  if ( $action =~ /M/ ) {
369                                  log_system("$cvs delete $path", "cvs delete of $path failed");                          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 {
403                                            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                                    }
410                          } else {                          } else {
411                                  print "WARNING: $path is not present, skipping...\n";                                  print "WARNING: $path is not present, skipping...\n";
412                                  undef $path;                                  undef $path;
413                          }                          }
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
# Line 322  foreach my $e (@{$xml->{'logentry'}}) { Line 421  foreach my $e (@{$xml->{'logentry'}}) {
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  }  }

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

  ViewVC Help
Powered by ViewVC 1.1.26