Dobrica Pavlinušić's random unstructured stuff
[PATCH] svn2cvs doesn`t handle directory deletion (and other stories): Revision 1

From: Dobrica Pavlinusic
Date: Date: Wed, 23 Jan 2008 18:28:37 +0100
Hi,

Your svn2cvs testsuite failed on my machine, and I had to fix both the testsuite and the perl script. I assume it must have worked for you the last time you ran your testsuite, and I have no clue why. I`m using CVS 1.12.9.

  > cd svn2cvs/src

Running ./test.sh, the first abnormal behaviour I notice is at svn commit 14, "remove everything":
  ...
  + svn rm $svnco/dir/*
  D /dev/shm/test-svn-co/dir/bar
  D /dev/shm/test-svn-co/dir/baz
  D /dev/shm/test-svn-co/dir/keep
  D /dev/shm/test-svn-co/dir/keep-dir
  D /dev/shm/test-svn-co/dir/l1/bar
  D /dev/shm/test-svn-co/dir/l1/baz
  D /dev/shm/test-svn-co/dir/l1/l2/bar
  D /dev/shm/test-svn-co/dir/l1/l2/baz
  D /dev/shm/test-svn-co/dir/l1/l2/l3/bar
  D /dev/shm/test-svn-co/dir/l1/l2/l3/baz
  D /dev/shm/test-svn-co/dir/l1/l2/l3
  D /dev/shm/test-svn-co/dir/l1/l2
  D /dev/shm/test-svn-co/dir/l1
  D /dev/shm/test-svn-co/dir/skip_add
  D /dev/shm/test-svn-co/dir/with space
  + svn revert $svnco/dir/keep $svnco/dir/keep-dir/keep
  Reverted '/dev/shm/test-svn-co/dir/keep'
  Skipped '/dev/shm/test-svn-co/dir/keep-dir/keep'
  + svn commit -m 'remove everything' $svnco
  Deleting /dev/shm/test-svn-co/dir/bar
  Deleting /dev/shm/test-svn-co/dir/baz
  Deleting /dev/shm/test-svn-co/dir/keep-dir
  Deleting /dev/shm/test-svn-co/dir/l1
  Deleting /dev/shm/test-svn-co/dir/skip_add
  Deleting /dev/shm/test-svn-co/dir/with space
  

  Committed revision 14.
  ...

The interesting lines are:
  Skipped '/dev/shm/test-svn-co/dir/keep-dir/keep'
  ...
  Deleting /dev/shm/test-svn-co/dir/keep-dir

It`s obvious from the "svn revert" line that you never intended keep-dir to be deleted, yet it does gets deleted. I`m using svn 1.3.1 (r19032).

This time, the fix is in the testsuite itself:
  --- svn2cvs/src/test.sh 2007-11-01 19:41:19.000000000 +0000
  +++ svn2cvs/src/test.sh 2007-11-01 19:49:37.000000000 +0000
  @@ -145,7 +145,7 @@
   test
   

   svn rm $svn_co/dir/* || exit
  -svn revert $svn_co/dir/keep $svn_co/dir/keep-dir/keep
  +svn revert $svn_co/dir/keep $svn_co/dir/keep-dir{,/keep}
   svn commit -m "remove everything" $svn_co || exit
   

   test

The relevant commit in the new testsuite now runs as follows:
  ...
  + svn rm $svnco/dir/*
  D /dev/shm/test-svn-co/dir/bar
  D /dev/shm/test-svn-co/dir/baz
  D /dev/shm/test-svn-co/dir/keep
  D /dev/shm/test-svn-co/dir/keep-dir
  D /dev/shm/test-svn-co/dir/l1/bar
  D /dev/shm/test-svn-co/dir/l1/baz
  D /dev/shm/test-svn-co/dir/l1/l2/bar
  D /dev/shm/test-svn-co/dir/l1/l2/baz
  D /dev/shm/test-svn-co/dir/l1/l2/l3/bar
  D /dev/shm/test-svn-co/dir/l1/l2/l3/baz
  D /dev/shm/test-svn-co/dir/l1/l2/l3
  D /dev/shm/test-svn-co/dir/l1/l2
  D /dev/shm/test-svn-co/dir/l1
  D /dev/shm/test-svn-co/dir/skip_add
  D /dev/shm/test-svn-co/dir/with space
  + svn revert $svnco/dir/keep $svnco/dir/keep-dir $svnco/dir/keep-dir/keep
  Reverted '/dev/shm/test-svn-co/dir/keep'
  Reverted '/dev/shm/test-svn-co/dir/keep-dir'
  Skipped '/dev/shm/test-svn-co/dir/keep-dir/keep'
  + svn commit -m 'remove everything' $svnco
  Deleting /dev/shm/test-svn-co/dir/bar
  Deleting /dev/shm/test-svn-co/dir/baz
  Deleting /dev/shm/test-svn-co/dir/l1
  Deleting /dev/shm/test-svn-co/dir/skip_add
  Deleting /dev/shm/test-svn-co/dir/with space

  Committed revision 14.
  ...

This time, the "skip" is legitimate. The testsuite still fails, though:
  ...
  + diff -x '.svn*' -x CVS -urw $svnco/dir/ $cvsco/dir/
  Only in /dev/shm/test-cvs-co//dir/: l1
  Only in /dev/shm/test-cvs-co//dir/: with space
  + exit

It again failed at this problematic last commit, "remove everything":
  ...
  Starting after revision 13
  ## svn export --force -q -r 14 file:///dev/shm/test-svn-rep//dir /tmp/checkoutGGpuy/dir
  NOTICE: using /dir as directory for svn

  -------------------------------------------------------------------------------
  r 14| gelineaus | 2007-11-01T20:22:39.115442Z

  remove everything
  svn2cvs: D /dir/bar
  #### remove file: bar at ./svn2cvs.pl line 402.
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'bar'
  cvs remove: scheduling `bar' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'bar'
  /dev/shm/test-cvs-rep/dir/bar,v <-
bar
  new revision: delete; previous revision: 1.1
  svn2cvs: D /dir/with space
  WARNING: with space is not present in CVS, skipping...
  svn2cvs: D /dir/baz
  #### remove file: baz at ./svn2cvs.pl line 402.
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'baz'
  cvs remove: scheduling `baz' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'baz'
  /dev/shm/test-cvs-rep/dir/baz,v <-
baz
  new revision: delete; previous revision: 1.1
  svn2cvs: D /dir/l1
  WARNING: l1 is not present in CVS, skipping...
  svn2cvs: D /dir/skip_add
  #### remove file: skip_add at ./svn2cvs.pl line 402.
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'skip_add'
  cvs remove: scheduling `skip_add' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'skip_add'
  /dev/shm/test-cvs-rep/dir/skip_add,v <-
skip_add
  new revision: delete; previous revision: 1.1
  commit ignored, no files
  subversion revision 14 commited to CVS
  /dev/shm/test-cvs-rep/dir/.svnrev,v <-- .svnrev
  new revision: 1.15; previous revision: 1.14
  + update_all
  + update_svn
  + svn update /dev/shm/test-svn-co/
  At revision 14.
  + update_cvs
  + cd /dev/shm/test-cvs-co/
  + cvs f update -d dir
  cvs update: Updating dir
  U dir/.svnrev
  cvs update: `dir/bar' is no longer in the repository
  cvs update: `dir/baz' is no longer in the repository
  cvs update: `dir/skip_add' is no longer in the repository
  cvs update: Updating dir/keep-dir
  cvs update: Updating dir/l1
  cvs update: Updating dir/l1/l2
  cvs update: Updating dir/l1/l2/l3
  cvs update: Updating dir/with space
  + cd

  + diff -x '.svn*' -x CVS -urw /dev/shm/test-svn-co//dir/ /dev/shm/test-cvs-co//dir/
  Only in /dev/shm/test-cvs-co//dir/: l1
  Only in /dev/shm/test-cvs-co//dir/: with space
  + exit

The relevant lines are:
  ...
  svn2cvs: D /dir/with space
  WARNING: with space is not present in CVS, skipping...
  ...
  svn2cvs: D /dir/l1
  WARNING: l1 is not present in CVS, skipping...
  ...
  Only in /dev/shm/test-cvs-co//dir/: l1
  Only in /dev/shm/test-cvs-co//dir/: with space
  + exit

In other words, svn2cvs.pl cannot see directories and refuses to remove them. This is because of the in_entries() function, which does not consider lines describing directories. Here`s the fix:
  --- svn2cvs/src/svn2cvs.pl 2007-11-01 20:21:22.000000000 +0000
  +++ svn2cvs/src/svn2cvs.pl 2007-11-01 20:36:52.000000000 +0000
  @@ -263,6 +263,7 @@ sub in_entries($) {
    || return 0; #die "no entries file: $dir/CVS/Entries";
    while (<$fh>) {
    return 1 if (m{^/$file/});
  + return 1 if (m{^D/$file/});
    }
    close($fh);
    return 0;

But the problematic last commit still fails:
  Starting after revision 13
  ## svn export --force -q -r 14 file:///dev/shm/test-svn-rep//dir /tmp/checkout1jkCl/dir
  NOTICE: using /dir as directory for svn

  -------------------------------------------------------------------------------
  r 14| gelineaus | 2007-11-01T20:53:32.113268Z

  remove everything
  svn2cvs: D /dir/bar
  #### remove file: bar at ./svn2cvs.pl line 403.
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'bar'
  cvs remove: scheduling `bar' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'bar'
  /dev/shm/test-cvs-rep/dir/bar,v <-
bar
  new revision: delete; previous revision: 1.1
  svn2cvs: D /dir/with space
  #### remove directory: with space at ./svn2cvs.pl line 383.
  #### entries(with space) => at ./svn2cvs.pl line 249.
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'with space'
  cvs remove: Removing with space
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'with space'
  cvs commit: Examining with space
  ## cvs -f -d /dev/shm/test-cvs-rep/ update -dP .
  cvs update: Updating .
  cvs update: Updating keep-dir
  cvs update: Updating l1
  cvs update: Updating l1/l2
  cvs update: Updating l1/l2/l3
  cvs update: Updating with space
  svn2cvs: D /dir/baz
  #### remove file: baz at ./svn2cvs.pl line 403.
  ## cvs -f -d /dev/shm/test-cvs-rep/ delete 'baz'
  cvs remove: scheduling `baz' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'baz'
  /dev/shm/test-cvs-rep/dir/baz,v <-
baz
  new revision: delete; previous revision: 1.1
  svn2cvs: D /dir/l1
  #### remove directory: l1 at ./svn2cvs.pl line 383.
  #### entries recurse into: l1/l2 at ./svn2cvs.pl line 239, <$fh> line 3.
  #### entries recurse into: l1/l2/l3 at ./svn2cvs.pl line 239, <> line 3.
  #### entries(l1/l2/l3) => bar|baz at ./svn2cvs.pl line 249.
  #### entries(l1/l2) => bar|baz|l3/bar|l3/baz|l3 at ./svn2cvs.pl line 249.
  #### entries(l1) => bar|baz|l2/bar|l2/baz|l2/l3/bar|l2/l3/baz|l2/l3|l2 at ./svn2cvs.pl line 249.
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'l1/bar'
  cvs remove: scheduling `l1/bar' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/bar'
  /dev/shm/test-cvs-rep/dir/l1/bar,v <-
bar
  new revision: delete; previous revision: 1.1
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'l1/baz'
  cvs remove: scheduling `l1/baz' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/baz'
  /dev/shm/test-cvs-rep/dir/l1/baz,v <-
baz
  new revision: delete; previous revision: 1.1
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'l1/l2/bar'
  cvs remove: scheduling `l1/l2/bar' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/l2/bar'
  /dev/shm/test-cvs-rep/dir/l1/l2/bar,v <-
bar
  new revision: delete; previous revision: 1.2
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'l1/l2/baz'
  cvs remove: scheduling `l1/l2/baz' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/l2/baz'
  /dev/shm/test-cvs-rep/dir/l1/l2/baz,v <-
baz
  new revision: delete; previous revision: 1.2
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'l1/l2/l3/bar'
  cvs remove: scheduling `l1/l2/l3/bar' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/l2/l3/bar'
  /dev/shm/test-cvs-rep/dir/l1/l2/l3/bar,v <-
bar
  new revision: delete; previous revision: 1.3
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'l1/l2/l3/baz'
  cvs remove: scheduling `l1/l2/l3/baz' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/l2/l3/baz'
  /dev/shm/test-cvs-rep/dir/l1/l2/l3/baz,v <-
baz
  new revision: delete; previous revision: 1.3
  ## cvs f -d /dev/shm/test-cvs-rep/ delete 'l1/l2/l3'
  cvs remove: Removing l1/l2/l3
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/l2/l3'
  cvs commit: Examining l1/l2/l3
  ## cvs -f -d /dev/shm/test-cvs-rep/ delete 'l1/l2'
  cvs remove: Removing l1/l2
  cvs remove: Removing l1/l2/l3
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1/l2'
  cvs commit: Examining l1/l2
  cvs commit: Examining l1/l2/l3
  ## cvs -f -d /dev/shm/test-cvs-rep/ delete 'l1'
  cvs remove: Removing l1
  cvs remove: Removing l1/l2
  cvs remove: Removing l1/l2/l3
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1'
  cvs commit: Examining l1
  cvs commit: Examining l1/l2
  cvs commit: Examining l1/l2/l3
  ## cvs -f -d /dev/shm/test-cvs-rep/ update -dP .
  cvs update: Updating .
  cvs update: Updating keep-dir
  cvs update: Updating l1
  cvs update: Updating l1/l2
  cvs update: Updating l1/l2/l3
  cvs update: Updating with space
  svn2cvs: D /dir/skip_add
  #### remove file: skip_add at ./svn2cvs.pl line 403.
  ## cvs -f -d /dev/shm/test-cvs-rep/ delete 'skip_add'
  cvs remove: scheduling `skip_add' for removal
  cvs remove: use `cvs commit' to remove this file permanently
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'skip_add'
  /dev/shm/test-cvs-rep/dir/skip_add,v <-
skip_add
  new revision: delete; previous revision: 1.1
  commit ignored, no files
  subversion revision 14 commited to CVS
  /dev/shm/test-cvs-rep/dir/.svnrev,v <-- .svnrev
  new revision: 1.15; previous revision: 1.14
  + update_all
  + update_svn
  + svn update /dev/shm/test-svn-co/
  At revision 14.
  + update_cvs
  + cd /dev/shm/test-cvs-co/
  + cvs f update -d dir
  cvs update: Updating dir
  U dir/.svnrev
  cvs update: `dir/bar' is no longer in the repository
  cvs update: `dir/baz' is no longer in the repository
  cvs update: `dir/skip_add' is no longer in the repository
  cvs update: Updating dir/keep-dir
  cvs update: Updating dir/l1
  cvs update: `dir/l1/bar' is no longer in the repository
  cvs update: `dir/l1/baz' is no longer in the repository
  cvs update: Updating dir/l1/l2
  cvs update: `dir/l1/l2/bar' is no longer in the repository
  cvs update: `dir/l1/l2/baz' is no longer in the repository
  cvs update: Updating dir/l1/l2/l3
  cvs update: `dir/l1/l2/l3/bar' is no longer in the repository
  cvs update: `dir/l1/l2/l3/baz' is no longer in the repository
  cvs update: Updating dir/with space
  + cd

  + diff -x '.svn*' -x CVS -urw /dev/shm/test-svn-co//dir/ /dev/shm/test-cvs-co//dir/
  Only in /dev/shm/test-cvs-co//dir/: l1
  Only in /dev/shm/test-cvs-co//dir/: with space
  + exit

The relevant lines are:
  ...
  svn2cvs: D /dir/with space
  ## cvs -f -d /dev/shm/test-cvs-rep/ delete 'with space'
  cvs remove: Removing with space
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'with space'
  ...
  ## cvs -f -d /dev/shm/test-cvs-rep/ delete 'l1'
  cvs remove: Removing l1
  cvs remove: Removing l1/l2
  cvs remove: Removing l1/l2/l3
  ## cvs -f -d /dev/shm/test-cvs-rep/ commit -m 'remove everything' 'l1'
  ...
  Only in /dev/shm/test-cvs-co//dir/: l1
  Only in /dev/shm/test-cvs-co//dir/: with space
  + exit

Thus cvs was asked to delete the directories, but didn`t. This is no surprise, since CVS was designed not to remove directories. From the CVS manual:
  7.3 Removing directories
  

  In concept, removing directories is somewhat similar to removing files--you
  want the directory to not exist in your current working directories, but you
  also want to be able to retrieve old releases in which the directory existed.
  

  The way that you remove a directory is to remove all the files in it. You don't
  remove the directory itself; there is no way to do that. Instead you specify
  the '-P' option to cvs update or cvs checkout, which will cause CVS to remove
  empty directories from working directories. (Note that cvs export always removes
  empty directories.) Probably the best way to do this is to always specify '-P';
  if you want an empty directory then put a dummy file (for example '.keepme') in
  it to prevent '-P' from removing it.
  

  Note that '-P' is implied by the '-r' or '-D' options of checkout. This way, CVS
  will be able to correctly create the directory or not depending on whether the
  particular version you are checking out contains any files in that directory.

Thus the fix would be to automatically add a ".keepme" file upon directory creation, and to removed it (along with the other files in the directory) upon directory deletion. The testsuite also needs to be updated to use the "P" flag. Thus, my last patch:
  diff -r -uprN svn2cvs/src/svn2cvs.pl svn2cvs/src/svn2cvs.pl
  --
svn2cvs/src/svn2cvs.pl 2007-11-01 20:36:52.000000000 +0000
  +++ svn2cvs/src/svn2cvs.pl 2007-11-01 21:06:04.000000000 +0000
  @@ -104,7 +104,8 @@ sub add_dir($$) {
    next if in_entries($curr_dir);
    next if ( -e "$curr_dir/CVS" );
   

  + log_system( "touch '$curr_dir/.keepme'", "creation of .keepme file (to keep $curr_dir alive in CVS) failed" );
  - log_system( "$cvs add '$curr_dir'", "cvs add of $curr_dir failed" );
  + log_system( "$cvs add '$curr_dir' '$curr_dir/.keepme'", "cvs add of $curr_dir failed" );
    }
   }

  diff r -uprN svn2cvs/src/test.sh svn2cvs/src/test.sh
  --
svn2cvs/src/test.sh 2007-11-01 20:36:32.000000000 +0000
  +++ svn2cvs/src/test.sh 2007-11-01 21:06:39.000000000 +0000
  @@ 52,13 +52,13 @@ cd || exit
   

   rm Rf $cvs_co || exit
   mkdir $cvs_co || exit
  -cd $cvs_co && cvs -f co dir && cd
|| exit
  +cd $cvs_co && cvs f co -P dir && cd || exit
   

   function svn2cvs() {
    ./svn2cvs.pl file://$svn_rep/dir $cvs_rep dir || exit
   }
   function update_cvs() {
  - cd $cvs_co && cvs f update -d dir && cd || exit
  + cd $cvs_co && cvs f update -P -d dir && cd || exit
   }
   function update_svn() {
    svn update $svn_co || exit
  @@ 70,7 +70,7 @@ function update_all() {
   function test() {
    svn2cvs
    update_all
  
diff -x .svn\* -x CVS -urw $svn_co/dir/ $cvs_co/dir/ || exit
  + diff -x .svn\* -x CVS -x '\.keep' -urw $svn_co/dir/ $cvs_co/dir/ || exit
   }
   

   svn2cvs

And now the problematic test finally passes.

Hoping to be useful,

  • Samuel Gélineau