/[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 1 by dpavlin, Tue Mar 9 20:58:34 2004 UTC revision 16 by dpavlin, Mon Mar 15 12:21:32 2004 UTC
# Line 8  Line 8 
8  # http://raw.no/personal/blog  # http://raw.no/personal/blog
9  #  #
10  # 2004-03-09 Dobrica Pavlinusic <dpavlin@rot13.org>  # 2004-03-09 Dobrica Pavlinusic <dpavlin@rot13.org>
11    #
12    # documentation is after __END__
13    
14  use strict;  use strict;
15  use File::Temp qw/ tempdir /;  use File::Temp qw/ tempdir /;
16  use Data::Dumper;  use Data::Dumper;
17  use XML::Simple;  use XML::Simple;
18    
19  # get current user home directory  if (@ARGV < 2) {
20  my $HOME = $ENV{'HOME'} || die "can't get home directory!";          print "usage: $0 SVN_URL CVSROOT CVSREPOSITORY\n";
21            exit 1;
22    }
23    
24  # cvsroot directory  my ($SVNROOT,$CVSROOT, $CVSREP) = @ARGV;
25  my $CVSROOT="$HOME/x/cvsroot";  
26  # name of cvs repository to commit to  if ($SVNROOT !~ m,^[\w+]+:///*\w+,) {
27  my $CVSREP="webpac";          print "ERROR: invalid svn root $SVNROOT\n";
28            exit 1;
29  # svnroot directory  }
 my $SVNROOT="file://$HOME/private/svn/webpac/";  
 # name of respository  
 my $SVNREP="trunk";  
30    
31  my $TMPDIR=tempdir( "/tmp/checkoutXXXXX", CLEANUP => 1 );  my $TMPDIR=tempdir( "/tmp/checkoutXXXXX", CLEANUP => 1 );
32    
# Line 40  my $cvs="cvs -d $CVSROOT"; Line 41  my $cvs="cvs -d $CVSROOT";
41  sub log_system($$) {  sub log_system($$) {
42          my ($cmd,$errmsg) = @_;          my ($cmd,$errmsg) = @_;
43          print STDERR "## $cmd\n";          print STDERR "## $cmd\n";
44          system $cmd || die "$errmsg: $!";          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  # ok, now do the checkout          die "commit_svnrev needs revision" if (! defined($rev));
55    
56  log_system("$cvs checkout $CVSREP","cvs checkout failed");          open(SVNREV,"> .svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";
57            print SVNREV $rev;
58            close(SVNREV);
59    
60  # check if svnrev exists          my $path=".svnrev";
61    
62  if (! -e "$CVSREP/.svnrev") {          if ($add_new) {
63          print <<_USAGE_;                  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          Your CVS repository doesn't have $TMPDIR/$CVSREP/.svnrev file!  # ok, now do the checkout
72    
73          This file is used to commit changes to CVS repository with correct  log_system("$cvs -q checkout $CVSREP", "cvs checkout failed");
         logs from Subversion.  
74    
75          Please create .svnrec file with currect svn revision which  chdir($CVSREP) || die "can't cd to $TMPDIR/$CVSREP: $!";
         corresponds to version of checkouted tree and commit that file  
         to CVS repository, e.g.  
   
         echo 233 > .svnrev  
         cvs add .svnrev  
         cvs commit -m "subversion repository revision sync file" .svnrev  
76    
 _USAGE_  
         print "CVS Repository left in $TMPDIR/$CVSREP\n";  
         exit 1;  
 }  
77    
78    my $rev;
79    
80  open(SVNREV,"$CVSREP/.svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";  # check if svnrev exists
81  my $rev = <SVNREV>;  if (! -e ".svnrev") {
82  chomp($rev);          print <<_USAGE_;
 close(SVNREV);  
83    
84  print "Starting after revision $rev\n";  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  # sub to commit .svn rev file later  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  sub commit_svnrev($) {  _USAGE_
         my $rev = shift @_ || die "commit_svnrev needs revision";  
99    
100          open(SVNREV,"> $CVSREP/.svnrev") || die "can't open $TMPDIR/$CVSREP/.svnrev: $!";          print "svn revision corresponding to CVS [abort]: ";
101          print SVNREV $rev;          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);          close(SVNREV);
   
         my $msg="subversion revision $rev commited to CVS";  
         my $path=".svnrev";  
         print "$msg\n";  
         system "$cvs commit -m \"$msg\" $CVSREP/$path" || die "cvs commit of $path failed: $!";  
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  # 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  # loose multiple edits of same file and corresponding messages. On the
# Line 104  sub commit_svnrev($) { Line 125  sub commit_svnrev($) {
125  # case much about accuracy and completnes of logs there, this might  # case much about accuracy and completnes of logs there, this might
126  # be good. YMMV  # be good. YMMV
127  #  #
128  open(LOG, "svn log -r $rev:HEAD -v --xml $SVNROOT/$SVNREP |") || die "svn log for repository $SVNROOT/$SVNREP failed: $!";  open(LOG, "svn log -r $rev:HEAD -v --xml $SVNROOT |") || die "svn log for repository $SVNROOT failed: $!";
129  my $log;  my $log;
130  while(<LOG>) {  while(<LOG>) {
131          $log .= $_;          $log .= $_;
132  }  }
133  close(LOG);  close(LOG);
134    
135  my $xml = XMLin($log, ForceArray => [ 'path' ]);  my $xml = XMLin($log, ForceArray => [ 'logentry', 'path' ]);
   
136    
 =begin log_example  
137    
138  ------------------------------------------------------------------------  #=begin log_example
139  r256 | dpavlin | 2004-03-09 13:18:17 +0100 (Tue, 09 Mar 2004) | 2 lines  #
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  ported r254 from hidra branch  my $fmt = "\n" . "-" x 79 . "\nr%5s| %8s | %s\n\n%s\n";
148    
149  =cut  if (! $xml->{'logentry'}) {
150            print "no newer log entries in Subversion repostory. CVS is current\n";
151            exit 0;
152    }
153    
154  my $fmt = "\n" . "-" x 79 . "\nr%5s| %8s | %s\n\n%s\n";  # 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  foreach my $e (@{$xml->{'logentry'}}) {  foreach my $e (@{$xml->{'logentry'}}) {
174          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 (".$e->{'revision'}.")" if ($rev > $e->{'revision'});
175          $rev = $e->{'revision'};          $rev = $e->{'revision'};
176          log_system("svn export --force -q -r $rev $SVNROOT/$SVNREP $CVSREP", "svn export of revision $rev failed");          log_system("svn export --force -q -r $rev $SVNROOT $TMPDIR/$CVSREP", "svn export of revision $rev failed");
177    
178            # 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                            print "NOTICE: can't deduce svn dir from $SVNROOT - skipping\n";
187                            next;
188                    }
189            } until ($tmppath =~ m/^$SVNREP/);
190    
191            print "NOTICE: using $SVNREP as directory for svn\n";
192    
193          printf($fmt, $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'});          printf($fmt, $e->{'revision'}, $e->{'author'}, $e->{'date'}, $e->{'msg'});
194          foreach my $p (@{$e->{'paths'}->{'path'}}) {          foreach my $p (@{$e->{'paths'}->{'path'}}) {
# Line 138  foreach my $e (@{$xml->{'logentry'}}) { Line 198  foreach my $e (@{$xml->{'logentry'}}) {
198    
199                  # prepare path and message                  # prepare path and message
200                  my $file = $path;                  my $file = $path;
201                  $path =~ s,^/$SVNREP/*,, || die "BUG: can't strip SVNREP from path";                  $path =~ s,^$SVNREP/*,, || die "BUG: can't strip SVNREP from path";
202    
203                    if (! $path) {
204                            print "NOTICE: skipped this operation. Probably trunk creation\n";
205                            next;
206                    }
207    
208                  my $msg = $e->{'msg'};                  my $msg = $e->{'msg'};
209                  $msg =~ s/"/\\"/g;      # quote "                  $msg =~ s/'/'\\''/g;    # quote "
210    
211                  if ($action =~ /M/) {                  if ($action =~ /M/) {
212                          print "svn2cvs: modify $path -- nop\n";                          print "svn2cvs: modify $path -- nop\n";
213                  } elsif ($action =~ /A/) {                  } elsif ($action =~ /A/) {
214                          log_system("$cvs add -m \"$msg\" $CVSREP/$path", "cvs add of $path failed");                          if (-d $path) {
215                                    chdir($path) || die "can't cd into dir $path for import: $!";
216                                    log_system("$cvs import -d -m '$msg' $CVSREP/$path svn r$rev", "cvs import of $path failed");
217                                    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                            } 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                            } else {
225                                    in_entries($path) || log_system("$cvs add $path", "cvs add of $path failed");
226                            }
227                  } elsif ($action =~ /D/) {                  } elsif ($action =~ /D/) {
228                          log_system("$cvs delete -m \"$msg\" $CVSREP/$path", "cvs delete of $path failed");                          unlink $path || die "can't delete $path: $!";
229                            log_system("$cvs delete $path", "cvs delete of $path failed");
230                  } else {                  } else {
231                          print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";                          print "WARNING: action $action not implemented on $path. Bug or missing feature of $0\n";
232                  }                  }
233    
234                  # now commit changes                  # now commit changes
235                  log_system("$cvs commit -m \"$msg\" $CVSREP/$path", "cvs commit of $path failed");                  log_system("$cvs commit -m '$msg' $path", "cvs commit of $path failed");
236    
237          }          }
238    
# Line 162  foreach my $e (@{$xml->{'logentry'}}) { Line 241  foreach my $e (@{$xml->{'logentry'}}) {
241    
242  __END__  __END__
243    
244  svn export --force "$SVNROOT/$SVNREP" "$CVSREP"  =pod
245    
246  cd dotfiles  =head1 NAME
247    
248  for file in $(find -type f -not -path \*CVS\*); do  svn2cvs - save subversion commits to (read-only) cvs repository
249          FILE=$(basename $file)  
250          DIR=$(dirname $file)  =head1 SYNOPSIS
251          if ! grep -q "^/$FILE/" $DIR/CVS/Entries ; then  
252                  cvs add $file    ./svn2cvs.pl SVN_URL CVSROOT CVSREPOSITORY
253          fi  
254  done  Usage example (used to self-host this script):
255    
256  #cvs commit -m "Automatic commit from SVN"    ./svn2cvs.pl file:///home/dpavlin/private/svn/svn2cvs/trunk/ \
257                   :pserver:dpavlin@cvs.tigris.org:/cvs svn2cvs/src
258  #rm -rf $TMPDIR  
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    =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    =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    
 echo "cvs left in $TMPDIR"  

Legend:
Removed from v.1  
changed lines
  Added in v.16

  ViewVC Help
Powered by ViewVC 1.1.26