svn copy's behaviour when copying directories to paths that already exist does not quite match that of Linux's cp command.


This week at work we resumed development on a new software feature that we hadn't had time to look at since its initial proof-of-concept phase several months ago. We do feature development on feature-specific SVN branches, so the unfinished code was safely tucked away in its own pocket universe whilst we applied a whole ton of new functionality and API changes on the trunk in the meantime. Today it came time to update this branch so that its code was up-to-date and we could continue working.

Now, we also have a couple of levels of dependencies linked in by SVN externals:

project/trunk
   \
    |- dep1/trunk@SomeRecentRev
    |- dep2/trunk@SomeRecentRev

The proof-of-concept feature's project branch had a feature-specific branch for dep1, but not for dep2:

project/branches/thefeature
   \
    |- dep1/branches/thefeature
    |- dep2/trunk@SomeOldRev

How we caused a problem

Today, to clean things up and to obtain a full working environment for the new feature, we needed to create a branch for dev2 as well. Using version 1.6.6 of the command-line client:

svn copy \
   https://repohost/svn/dep2/trunk/ \
   https://repohost/svn/dep2/branches/thefeature/ \
   -m"Created new branch for thefeature development"

One would think that the result would be:

project/branches/thefeature
   \
    |- dep1/branches/thefeature
    |- dep2/branches/thefeature

and, well, it was.

However, something went wrong. Having done this, upon building project/branches/thefeature, it became evident that the code under dep2/branches/thefeature was old. Really old. Probably from around @SomeOldRev, dating back to development on the original proof-of-concept. But didn't we just copy from the trunk HEAD? Sure we did…

Mystery solved

Closer inspection revealed that there was now an unexpected subdirectory "trunk" underneath this outdated code, which contained the expected recent code:

project/branches/thefeature
   \
    |- dep1/branches/thefeature
    |- dep2/branches/thefeature
          \
           | old code
           | "trunk/"
                 \
                  | new code

Where on Earth did this come from?

Around this time it became known that there had in fact been an orphaned dep2/branches/thefeature already in existence from the days of @SomeOldRev that had never been used. Still, I'd guessed that svn copy's mechanism would follow the convention of Linux cp, which when copying directories has significance in trailing slashes on the source-dir. That is, with cp, if the destination directory already exists and you do not use a trailing slash on the source-dir, then you are actually putting a copy of source-dir inside dest-dir. But our svn copy invocation used trailing slashes on both the source and destination arguments!

Some chatter on the SVN mailing list included this:

I did a bit of research in the list archive, and came across this snippet,
posted by someone basically saying this was reasonable behaviour because
that's how *nix does it:

cp path/A path/B --> if path/B doesn't exist, creates path/B
                     if path/B does    exist, creates path/B/A 

with no mention of the trailing-slash rule. Indeed, from our experience, the above would seem to be the case whether you include the trailing slash or not.

Nuking the entire dep2/branches/thefeature directory and re-copying from trunk solved all the build problems and left the directory structure as expected.