Hello,

I had some unexpected behaviour from SVN following the commit of a merge operation. I believe I have found a bug in SVN.

I'm using version 1.6.12.

On the one hand, I don't think the bug is too bad, as it is something of a corner case. On the other hand, the result is a working copy that is out of synchronisation with the repository, which is a serious fault.

Below, I give a basic Perl script (which should work on either Windows or Linux) to demonstrate the problem, together with some output. If you don't wish to run the script, I hope that the sequence of commands to reproduce the problem is clear by just reading through from the "### create an empty repository" line.

Briefly, the steps to reproduce the problem are:
  * have a branch in which directories have been added
  * merge (reintegrate) a branch to the trunk, but don't commit yet
  * rename a subdirectory on the branch
  * merge to trunk again
  * commit

Now, the trunk in the repository has the subdirectory both as it was before and after the rename, but the working copy doesn't.

The script is:
__________________________________________________________________

#!/usr/bin/perl

use strict;
use File::Path;
use Cwd;

sub WriteFile ($$)
{
    my $fn = shift;
    my $str = shift;
    my $ok = open (my $fh, '>', $fn);
    if ($ok)
    {
        print $fh $str;
        close ($fh);
    }
}

sub ls ($)
{
    my $path = shift;
    my @files = <${path}/*>;
    foreach my $myfile (@files)
    {
        $myfile =~ s/^${path}\///;
        print "$myfile\n";
    }
}

my $data = 'data/';
my $repos_rel_path = "${data}repos/";
my $wc_tree = "${data}wc/";

my $content = "Contents of the file.\n";

mkdir $data;

my $repos_path = cwd . '/' . $repos_rel_path;
(my $repos_url = 'file:///' . $repos_path) =~ s/([^-._~A-Za-z0-9\/:])/sprintf("%%%02X", ord($1))/egs;

### create an empty repository and working copy
`svnadmin create $repos_rel_path` if ! -e $repos_rel_path;
`svn co $repos_url $wc_tree` if ! -e $wc_tree;

### add some directories and files
mkdir "${wc_tree}trunk/";
WriteFile ("${wc_tree}trunk/file.txt", $content);
`svn add "${wc_tree}trunk/"`;
`svn ci "${wc_tree}" -m "initial files"`;

### branch
`svn cp "${repos_url}trunk/" "${repos_url}branch/" -m "branch"`;
`svn up "${wc_tree}"`;

### possibly change some trunk files

### add some branch files
mkdir "${wc_tree}branch/olddir/";
mkdir "${wc_tree}branch/olddir/oldsubdir/";
WriteFile ("${wc_tree}branch/olddir/subfile.txt", $content);
WriteFile ("${wc_tree}branch/olddir/oldsubdir/sub2file.txt", $content);
`svn add "${wc_tree}branch/olddir"`;
`svn ci "${wc_tree}" -m "branch adds"`;

### possibly update branch with trunk (merge)

### reintegrate (merge) branch to trunk, but don't commit yet
`svn merge --reintegrate "^/branch/" "${wc_tree}trunk/"`;

### make and commit forgotten directory rename on branch
`svn mv "${wc_tree}branch/olddir/oldsubdir/" "${wc_tree}branch/olddir/newsubdir/"`;
`svn ci "${wc_tree}branch/" -m "branch mods"`;
`svn up "${wc_tree}"`;

### further merge branch to trunk
`svn merge "^/branch/" "${wc_tree}trunk/"`;

### resolve tree conflict (in favour of the move) and commit at last
`svn rm --force ${wc_tree}trunk/olddir/oldsubdir`;
`svn resolve --accept=working ${wc_tree}trunk/olddir/oldsubdir`;
`svn ci "${wc_tree}trunk/" -m "merge"`;
`svn up "${wc_tree}"`;

### display the result
print "\n\$ svn log\n"; print `svn log ${wc_tree}trunk/olddir/ -qvrHEAD`;
print "\n\$ ls wc\n"; ls "${wc_tree}trunk/olddir/";
print "\n\$ svn ls repos\n"; print `svn ls ${wc_tree}trunk/olddir/ -rHEAD`;

### reveal the synchronisation problem between wc and repos
### by manually updating the path that the wc doesn't know is missing
`svn up "${wc_tree}trunk/olddir/oldsubdir/"`;
print "\n\$ ls wc (2)\n"; ls "${wc_tree}trunk/olddir/";

### end of perl
__________________________________________________________________

The output from the script is:
__________________________________________________________________

$ svn log
------------------------------------------------------------------------
r5 | Rob | 2010-09-26 00:22:07 +0100 (Sun, 26 Sep 2010)
Changed paths:
   M /trunk
   A /trunk/olddir (from /branch/olddir:3)
   A /trunk/olddir/newsubdir (from /branch/olddir/newsubdir:4)
------------------------------------------------------------------------

$ ls wc
newsubdir
subfile.txt

$ svn ls repos
newsubdir/
oldsubdir/
subfile.txt

$ ls wc (2)
newsubdir
oldsubdir
subfile.txt
__________________________________________________________________

The output shows the mismatch between what's in a certain directory of the working copy (ls wc) and of the repository (svn ls repos).

Note in the log that in the final commit (r5), path ^/trunk/olddir/ is added from a revision prior to the merged rename. This is correct. I believe that there should also be changes within that added directory. So it should read something like this:

------------------------------------------------------------------------
r5 | Rob | 2010-09-26 00:22:07 +0100 (Sun, 26 Sep 2010)
Changed paths:
   M /trunk
   A /trunk/olddir (from /branch/olddir:3)
   D /trunk/olddir/oldsubdir
   A /trunk/olddir/newsubdir (from /branch/olddir/newsubdir:4)
------------------------------------------------------------------------

or better still, like this perhaps (with the later merge having moved the copy-from revision on):

------------------------------------------------------------------------
r5 | Rob | 2010-09-26 00:22:07 +0100 (Sun, 26 Sep 2010)
Changed paths:
   M /trunk
   A /trunk/olddir (from /branch/olddir:4)
------------------------------------------------------------------------


Regards,
Rob.

Reply via email to